From e052b01f9ba8cf0ed398a912fcbd444abd4bd9ed Mon Sep 17 00:00:00 2001 From: Denny Date: Thu, 2 Mar 2017 19:33:10 -0500 Subject: [PATCH 01/20] Resolves issue with the playlist saving all of the time due to audio streaming broadcast events. --- src/playlist/PlaylistManager.java | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/playlist/PlaylistManager.java b/src/playlist/PlaylistManager.java index d0e19c83..6ca21ecf 100644 --- a/src/playlist/PlaylistManager.java +++ b/src/playlist/PlaylistManager.java @@ -89,6 +89,7 @@ public class PlaylistManager implements ChannelEventListener @Override public void receive(AliasEvent t) { + //Save the playlist for all alias events schedulePlaylistSave(); } }); @@ -98,6 +99,7 @@ public class PlaylistManager implements ChannelEventListener @Override public void receive(ChannelMapEvent t) { + //Save the playlist for all channel map events schedulePlaylistSave(); } }); @@ -107,7 +109,23 @@ public class PlaylistManager implements ChannelEventListener @Override public void receive(BroadcastEvent broadcastEvent) { - schedulePlaylistSave(); + switch(broadcastEvent.getEvent()) + { + case CONFIGURATION_ADD: + case CONFIGURATION_CHANGE: + case CONFIGURATION_DELETE: + schedulePlaylistSave(); + break; + case BROADCASTER_ADD: + case BROADCASTER_QUEUE_CHANGE: + case BROADCASTER_STATE_CHANGE: + case BROADCASTER_STREAMED_COUNT_CHANGE: + case BROADCASTER_AGED_OFF_COUNT_CHANGE: + case BROADCASTER_DELETE: + default: + //Do nothing + break; + } } }); } From f51ae0b5dabace1438a497a69301e75b7db68939 Mon Sep 17 00:00:00 2001 From: Denny Date: Fri, 3 Mar 2017 05:56:32 -0500 Subject: [PATCH 02/20] Resolves issue with periodic instances of audio recorders not being shutdown correctly. Adds recorder monitor as a fail-safe to automatically shutdown recorders that don't receive an end-call indication. --- src/record/RecorderManager.java | 120 +++++-- src/record/wave/RealBufferWaveRecorder.java | 380 ++++++++++---------- src/record/wave/WaveWriter.java | 60 ++-- 3 files changed, 296 insertions(+), 264 deletions(-) diff --git a/src/record/RecorderManager.java b/src/record/RecorderManager.java index 871fe914..32957c31 100644 --- a/src/record/RecorderManager.java +++ b/src/record/RecorderManager.java @@ -29,79 +29,102 @@ import util.ThreadPool; import java.io.File; import java.util.HashMap; +import java.util.Iterator; import java.util.Map; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; public class RecorderManager implements Listener { private static final Logger mLog = LoggerFactory.getLogger(RecorderManager.class); public static final int AUDIO_SAMPLE_RATE = 8000; + public static final long IDLE_RECORDER_REMOVAL_THRESHOLD = 6000; //6 seconds private Map mRecorders = new HashMap<>(); + private ScheduledFuture mRecorderMonitorFuture; private boolean mCanStartNewRecorders = true; /** - * Manages all audio recording for all processing channels. Reconstructs audio streams and distributes channel - * audio to each of the recorders, starting and stopping the recorders as needed. + * Audio recording manager. Monitors stream of audio packets produced by decoding channels and automatically starts + * audio recorders when the channel's metadata designates a call as recordable. Routes call audio to each recorder + * based on audio packet metadata. Recorders are shutdown when the channel sends an end-call audio packet + * indicating that the call is complete. A separate recording monitor periodically checks for idled recorders to + * be stopped for cases when the channel fails to send an end-call audio packet. */ public RecorderManager() { + mRecorderMonitorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new RecorderMonitor(), 2, + 2, TimeUnit.SECONDS); } + /** + * Prepares this class for shutdown + */ public void dispose() { + if(mRecorderMonitorFuture != null) + { + mRecorderMonitorFuture.cancel(true); + } } + /** + * Primary ingest point for audio packets from all decoding channels + * @param audioPacket to process + */ @Override public void receive(AudioPacket audioPacket) { - if(audioPacket.hasMetadata() && - audioPacket.getMetadata().isRecordable()) + if(audioPacket.hasMetadata() && audioPacket.getMetadata().isRecordable()) { String identifier = audioPacket.getMetadata().getUniqueIdentifier(); - if(mRecorders.containsKey(identifier)) + synchronized(mRecorders) { - RealBufferWaveRecorder recorder = mRecorders.get(identifier); - - if(audioPacket.getType() == AudioPacket.Type.AUDIO) + if(mRecorders.containsKey(identifier)) { - recorder.receive(audioPacket.getAudioBuffer()); - } - else if(audioPacket.getType() == AudioPacket.Type.END) - { - RealBufferWaveRecorder finished = mRecorders.remove(identifier); - finished.stop(); - } - } - else if(audioPacket.getType() == AudioPacket.Type.AUDIO) - { - if(mCanStartNewRecorders) - { - String filePrefix = getFilePrefix(audioPacket); + RealBufferWaveRecorder recorder = mRecorders.get(identifier); - RealBufferWaveRecorder recorder = null; - - try + if(audioPacket.getType() == AudioPacket.Type.AUDIO) { - recorder = new RealBufferWaveRecorder(AUDIO_SAMPLE_RATE, filePrefix); - - recorder.start(ThreadPool.SCHEDULED); - recorder.receive(audioPacket.getAudioBuffer()); - mRecorders.put(identifier, recorder); } - catch(Exception ioe) + else if(audioPacket.getType() == AudioPacket.Type.END) { - mCanStartNewRecorders = false; + RealBufferWaveRecorder finished = mRecorders.remove(identifier); + finished.stop(); + } + } + else if(audioPacket.getType() == AudioPacket.Type.AUDIO) + { + if(mCanStartNewRecorders) + { + String filePrefix = getFilePrefix(audioPacket); - mLog.error("Error attempting to start new audio wave recorder. All (future) audio recording " + - "is disabled", ioe); + RealBufferWaveRecorder recorder = null; - if(recorder != null) + try { - recorder.stop(); + recorder = new RealBufferWaveRecorder(AUDIO_SAMPLE_RATE, filePrefix); + + recorder.start(ThreadPool.SCHEDULED); + + recorder.receive(audioPacket.getAudioBuffer()); + mRecorders.put(identifier, recorder); + } + catch(Exception ioe) + { + mCanStartNewRecorders = false; + + mLog.error("Error attempting to start new audio wave recorder. All (future) audio recording " + + "is disabled", ioe); + + if(recorder != null) + { + recorder.stop(); + } } } } @@ -150,4 +173,31 @@ public class RecorderManager implements Listener return new ComplexBufferWaveRecorder(AUDIO_SAMPLE_RATE, sb.toString()); } + + /** + * Monitors currently running recorders and removes/closes any recorders that are idle more than 6 seconds + */ + public class RecorderMonitor implements Runnable + { + @Override + public void run() + { + synchronized(mRecorders) + { + Iterator> it = mRecorders.entrySet().iterator(); + + while(it.hasNext()) + { + Map.Entry entry = it.next(); + + if(entry.getValue().getLastBufferReceived() + IDLE_RECORDER_REMOVAL_THRESHOLD < System.currentTimeMillis()) + { + mLog.info("Removing idle recorder [" + entry.getKey() + "]"); + it.remove(); + entry.getValue().stop(); + } + } + } + } + } } diff --git a/src/record/wave/RealBufferWaveRecorder.java b/src/record/wave/RealBufferWaveRecorder.java index ac17988a..763c9ded 100644 --- a/src/record/wave/RealBufferWaveRecorder.java +++ b/src/record/wave/RealBufferWaveRecorder.java @@ -1,22 +1,33 @@ /******************************************************************************* - * 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 + * 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 record.wave; +import module.Module; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import sample.ConversionUtils; +import sample.Listener; +import sample.real.IFilteredRealBufferListener; +import sample.real.RealBuffer; +import util.TimeStamp; + +import javax.sound.sampled.AudioFormat; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; @@ -26,206 +37,175 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import javax.sound.sampled.AudioFormat; - -import module.Module; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import sample.ConversionUtils; -import sample.Listener; -import sample.real.IFilteredRealBufferListener; -import sample.real.RealBuffer; -import util.TimeStamp; - /** * WAVE audio recorder module for recording real sample buffers to a wave file */ -public class RealBufferWaveRecorder extends Module - implements IFilteredRealBufferListener, Listener +public class RealBufferWaveRecorder extends Module + implements IFilteredRealBufferListener, Listener { - private final static Logger mLog = - LoggerFactory.getLogger( RealBufferWaveRecorder.class ); + private final static Logger mLog = LoggerFactory.getLogger(RealBufferWaveRecorder.class); private WaveWriter mWriter; private String mFilePrefix; private Path mFile; - private AudioFormat mAudioFormat; - - private BufferProcessor mBufferProcessor; - private ScheduledFuture mProcessorHandle; - private LinkedBlockingQueue mBuffers = - new LinkedBlockingQueue<>( 500 ); - private long mLastBufferReceived; - - private AtomicBoolean mRunning = new AtomicBoolean(); - - public RealBufferWaveRecorder( int sampleRate, String filePrefix ) - { - mAudioFormat = new AudioFormat( sampleRate, //SampleRate - 16, //Sample Size - 1, //Channels - true, //Signed - false ); //Little Endian + private AudioFormat mAudioFormat; - mFilePrefix = filePrefix; - } + private BufferProcessor mBufferProcessor; + private ScheduledFuture mProcessorHandle; + private LinkedBlockingQueue mBuffers = new LinkedBlockingQueue<>(500); + private long mLastBufferReceived; - /** - * Timestamp of when the latest buffer was received by this recorder - */ - public long getLastBufferReceived() - { - return mLastBufferReceived; - } + private AtomicBoolean mRunning = new AtomicBoolean(); - public Path getFile() - { - return mFile; - } - - public void start( ScheduledExecutorService executor ) - { - if( mRunning.compareAndSet( false, true ) ) - { - if( mBufferProcessor == null ) - { - mBufferProcessor = new BufferProcessor(); - } + public RealBufferWaveRecorder(int sampleRate, String filePrefix) + { + mAudioFormat = new AudioFormat(sampleRate, //SampleRate + 16, //Sample Size + 1, //Channels + true, //Signed + false); //Little Endian - try - { - StringBuilder sb = new StringBuilder(); - sb.append( mFilePrefix ); - sb.append( "_" ); - sb.append( TimeStamp.getLongTimeStamp( "_" ) ); - sb.append( ".wav" ); - - mFile = Paths.get( sb.toString() ); + mFilePrefix = filePrefix; + } - mWriter = new WaveWriter( mAudioFormat, mFile ); + /** + * Indicates if the recorder is currently running. + */ + public boolean isRunning() + { + return mRunning.get(); + } + + /** + * Timestamp of when the latest buffer was received by this recorder + */ + public long getLastBufferReceived() + { + return mLastBufferReceived; + } + + public Path getFile() + { + return mFile; + } + + public void start(ScheduledExecutorService executor) + { + if(mRunning.compareAndSet(false, true)) + { + if(mBufferProcessor == null) + { + mBufferProcessor = new BufferProcessor(); + } + + try + { + StringBuilder sb = new StringBuilder(); + sb.append(mFilePrefix); + sb.append("_"); + sb.append(TimeStamp.getLongTimeStamp("_")); + sb.append(".wav"); + + mFile = Paths.get(sb.toString()); + + mWriter = new WaveWriter(mAudioFormat, mFile); /* Schedule the processor to run every 500 milliseconds */ - mProcessorHandle = executor.scheduleAtFixedRate( - mBufferProcessor, 0, 500, TimeUnit.MILLISECONDS ); - } - catch( IOException io ) - { - mLog.error( "Error starting real buffer recorder", io ); - } - } - } - - public void stop() - { - /* Signal the buffer processor to end with a poison pill */ - receive( new PoisonPill() ); - - mRunning.set( false ); - } - - @Override - public void receive( RealBuffer buffer ) - { - if( mRunning.get() ) - { - boolean success = mBuffers.offer( buffer ); - - if( !success ) - { - mLog.error( "recorder buffer overflow - purging [" + - mFile.toFile().getAbsolutePath() + "]" ); - - mBuffers.clear(); - } - - mLastBufferReceived = System.currentTimeMillis(); - } - } - - @Override - public Listener getFilteredRealBufferListener() - { - return this; - } - - @Override - public void dispose() - { - stop(); - } - - @Override - public void reset() - { - } - - public class BufferProcessor implements Runnable - { - public void run() - { - try - { - RealBuffer buffer = mBuffers.poll(); - - while( buffer != null ) - { - if( buffer instanceof PoisonPill ) - { - buffer = null; - - mBuffers.clear(); - - if( mWriter != null ) - { - try - { - mWriter.close(); - } - catch( IOException io ) - { - mLog.error( "Error stopping complex wave recorder [" + - getFile() + "]", io ); - } - } - - if( mProcessorHandle != null ) - { - mProcessorHandle.cancel( true ); - } - - mProcessorHandle = null; - mWriter = null; - mFile = null; - } - else - { - mWriter.write( ConversionUtils.convertToSigned16BitSamples( buffer ) ); - buffer = mBuffers.poll(); - } - } + mProcessorHandle = executor.scheduleAtFixedRate(mBufferProcessor, 0, 500, TimeUnit.MILLISECONDS); } - catch ( IOException ioe ) - { - /* Stop this module if/when we get an IO exception */ - mBuffers.clear(); - stop(); - - mLog.error( "IOException while trying to write to the wave " - + "writer", ioe ); - } - } + catch(IOException io) + { + mLog.error("Error starting real buffer recorder", io); + } + } + } + + public void stop() + { + if(mRunning.compareAndSet(true, false)) + { + try + { + write(); + + if(mWriter != null) + { + mWriter.close(); + mWriter = null; + } + } + catch(IOException ioe) + { + mLog.error("Error writing final audio buffers to recording during shutdown", ioe); + } + } + } + + @Override + public void receive(RealBuffer buffer) + { + if(mRunning.get()) + { + boolean success = mBuffers.offer(buffer); + + if(!success) + { + mLog.error("recorder buffer overflow - purging [" + mFile.toFile().getAbsolutePath() + "]"); + mBuffers.clear(); + } + + mLastBufferReceived = System.currentTimeMillis(); + } + } + + @Override + public Listener getFilteredRealBufferListener() + { + return this; + } + + @Override + public void dispose() + { + stop(); + } + + @Override + public void reset() + { + } + + /** + * Writes all audio currently in the queue to the file + * @throws IOException if there are any errors writing the audio + */ + private void write() throws IOException + { + RealBuffer buffer = mBuffers.poll(); + + while(buffer != null) + { + mWriter.write(ConversionUtils.convertToSigned16BitSamples(buffer)); + buffer = mBuffers.poll(); + } + } + + public class BufferProcessor implements Runnable + { + public void run() + { + try + { + write(); + } + catch(IOException ioe) + { + /* Stop this module if/when we get an IO exception */ + mBuffers.clear(); + stop(); + + mLog.error("IO Exception while trying to write to the wave writer", ioe); + } + } } - - /** - * This is used as a sentinel value to signal the buffer processor to end - */ - public class PoisonPill extends RealBuffer - { - public PoisonPill() - { - super( new float[ 1 ] ); - } - } } diff --git a/src/record/wave/WaveWriter.java b/src/record/wave/WaveWriter.java index a7733e69..645cbf5c 100644 --- a/src/record/wave/WaveWriter.java +++ b/src/record/wave/WaveWriter.java @@ -1,19 +1,20 @@ /******************************************************************************* - * 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 + * 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 record.wave; @@ -34,10 +35,7 @@ import javax.sound.sampled.AudioFormat; public class WaveWriter implements AutoCloseable { -// private final static Logger mLog = LoggerFactory.getLogger( WaveWriter.class ); - - private static final Pattern FILENAME_PATTERN = - Pattern.compile( "(.*_)(\\d+)(\\.wav)" ); + private static final Pattern FILENAME_PATTERN = Pattern.compile( "(.*_)(\\d+)(\\.wav)" ); public static final long MAX_WAVE_SIZE = 2l * (long)Integer.MAX_VALUE; private AudioFormat mAudioFormat; @@ -58,8 +56,7 @@ public class WaveWriter implements AutoCloseable * @param maxSize - maximum file size ( range: 1 - 4,294,967,294 bytes ) * @throws IOException - if there are any IO issues */ - public WaveWriter( AudioFormat format, Path file, long maxSize ) - throws IOException + public WaveWriter( AudioFormat format, Path file, long maxSize ) throws IOException { Validate.isTrue(format != null); Validate.isTrue( file != null ); @@ -102,15 +99,11 @@ public class WaveWriter implements AutoCloseable while( Files.exists( mFile ) ) { - mFile = Paths.get( mFile.toFile().getAbsolutePath() - .replace( ".wav", "_" + version + ".wav" ) ); - + mFile = Paths.get( mFile.toFile().getAbsolutePath().replace( ".wav", "_" + version + ".wav" ) ); version++; } - Files.createFile( mFile ); - - mFileChannel = (FileChannel.open( mFile, StandardOpenOption.WRITE ) ); + mFileChannel = (FileChannel.open( mFile, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW ) ); ByteBuffer header = WaveUtils.getWaveHeader( mAudioFormat ); @@ -129,9 +122,18 @@ public class WaveWriter implements AutoCloseable { mFileChannel.force( true ); mFileChannel.close(); + mFileChannel = null; } - - /** + + @Override + protected void finalize() throws IOException + { + mFileChannel.force( true ); + mFileChannel.close(); + mFileChannel = null; + } + + /** * Writes the buffer contents to the file. Assumes that the buffer is full * and the first byte of data is at position 0. */ From 980ad72d4dd82c2f4ef8f2ee0462dadca1a96dd1 Mon Sep 17 00:00:00 2001 From: Denny Date: Fri, 3 Mar 2017 06:01:09 -0500 Subject: [PATCH 03/20] Incrementing build to 0.3.0 beta7 --- build/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.xml b/build/build.xml index f44429b9..6105114e 100644 --- a/build/build.xml +++ b/build/build.xml @@ -7,7 +7,7 @@ - + From a877afccebf3a4ece6bc34eacbbcffd76400802f Mon Sep 17 00:00:00 2001 From: Denny Date: Fri, 3 Mar 2017 10:01:32 -0500 Subject: [PATCH 04/20] Updates buffer processing in recorder manager so that we're not blocking any of the decoding channel producer threads that are trying to push audio packets to the recorder manager. Adds an overflowable packet queue and services the queue once a second followed by an idle recorder check (and disposal). --- src/record/RecorderManager.java | 158 +++++++++++++++++++++----------- 1 file changed, 105 insertions(+), 53 deletions(-) diff --git a/src/record/RecorderManager.java b/src/record/RecorderManager.java index 32957c31..cb30aa08 100644 --- a/src/record/RecorderManager.java +++ b/src/record/RecorderManager.java @@ -25,11 +25,15 @@ import properties.SystemProperties; import record.wave.ComplexBufferWaveRecorder; import record.wave.RealBufferWaveRecorder; import sample.Listener; +import sample.OverflowableTransferQueue; +import sample.real.IOverflowListener; import util.ThreadPool; import java.io.File; +import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -42,7 +46,8 @@ public class RecorderManager implements Listener public static final long IDLE_RECORDER_REMOVAL_THRESHOLD = 6000; //6 seconds private Map mRecorders = new HashMap<>(); - private ScheduledFuture mRecorderMonitorFuture; + private OverflowableTransferQueue mAudioPacketQueue = new OverflowableTransferQueue<>(1000, 100); + private ScheduledFuture mBufferProcessorFuture; private boolean mCanStartNewRecorders = true; @@ -55,8 +60,24 @@ public class RecorderManager implements Listener */ public RecorderManager() { - mRecorderMonitorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new RecorderMonitor(), 2, - 2, TimeUnit.SECONDS); + mBufferProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new BufferProcessor(), 0, + 1, TimeUnit.SECONDS); + + mAudioPacketQueue.setOverflowListener(new IOverflowListener() + { + @Override + public void sourceOverflow(boolean overflow) + { + if(overflow) + { + mLog.warn("overflow - audio packets will be dropped until recording catches up"); + } + else + { + mLog.info("audio recorder packet processing has returned to normal"); + } + } + }); } /** @@ -64,9 +85,9 @@ public class RecorderManager implements Listener */ public void dispose() { - if(mRecorderMonitorFuture != null) + if(mBufferProcessorFuture != null) { - mRecorderMonitorFuture.cancel(true); + mBufferProcessorFuture.cancel(true); } } @@ -79,56 +100,97 @@ public class RecorderManager implements Listener { if(audioPacket.hasMetadata() && audioPacket.getMetadata().isRecordable()) { - String identifier = audioPacket.getMetadata().getUniqueIdentifier(); + mAudioPacketQueue.offer(audioPacket); + } + } - synchronized(mRecorders) + /** + * Process any queued audio buffers and dispatch them to the audio recorders + */ + private void processBuffers() + { + List audioPackets = new ArrayList<>(); + + mAudioPacketQueue.drainTo(audioPackets, 50); + + while(!audioPackets.isEmpty()) + { + for(AudioPacket audioPacket: audioPackets) { - if(mRecorders.containsKey(identifier)) + String identifier = audioPacket.getMetadata().getUniqueIdentifier(); + + synchronized(mRecorders) { - RealBufferWaveRecorder recorder = mRecorders.get(identifier); - - if(audioPacket.getType() == AudioPacket.Type.AUDIO) + if(mRecorders.containsKey(identifier)) { - recorder.receive(audioPacket.getAudioBuffer()); - } - else if(audioPacket.getType() == AudioPacket.Type.END) - { - RealBufferWaveRecorder finished = mRecorders.remove(identifier); - finished.stop(); - } - } - else if(audioPacket.getType() == AudioPacket.Type.AUDIO) - { - if(mCanStartNewRecorders) - { - String filePrefix = getFilePrefix(audioPacket); + RealBufferWaveRecorder recorder = mRecorders.get(identifier); - RealBufferWaveRecorder recorder = null; - - try + if(audioPacket.getType() == AudioPacket.Type.AUDIO) { - recorder = new RealBufferWaveRecorder(AUDIO_SAMPLE_RATE, filePrefix); - - recorder.start(ThreadPool.SCHEDULED); - recorder.receive(audioPacket.getAudioBuffer()); - mRecorders.put(identifier, recorder); } - catch(Exception ioe) + else if(audioPacket.getType() == AudioPacket.Type.END) { - mCanStartNewRecorders = false; + RealBufferWaveRecorder finished = mRecorders.remove(identifier); + finished.stop(); + } + } + else if(audioPacket.getType() == AudioPacket.Type.AUDIO) + { + if(mCanStartNewRecorders) + { + String filePrefix = getFilePrefix(audioPacket); - mLog.error("Error attempting to start new audio wave recorder. All (future) audio recording " + - "is disabled", ioe); + RealBufferWaveRecorder recorder = null; - if(recorder != null) + try { - recorder.stop(); + recorder = new RealBufferWaveRecorder(AUDIO_SAMPLE_RATE, filePrefix); + + recorder.start(ThreadPool.SCHEDULED); + + recorder.receive(audioPacket.getAudioBuffer()); + mRecorders.put(identifier, recorder); + } + catch(Exception ioe) + { + mCanStartNewRecorders = false; + + mLog.error("Error attempting to start new audio wave recorder. All (future) audio recording " + + "is disabled", ioe); + + if(recorder != null) + { + recorder.stop(); + } } } } } } + + audioPackets.clear(); + mAudioPacketQueue.drainTo(audioPackets, 50); + } + } + + /** + * Removes recorders that have not received any new audio buffers in the last 6 seconds. + */ + private void removeIdleRecorders() + { + Iterator> it = mRecorders.entrySet().iterator(); + + while(it.hasNext()) + { + Map.Entry entry = it.next(); + + if(entry.getValue().getLastBufferReceived() + IDLE_RECORDER_REMOVAL_THRESHOLD < System.currentTimeMillis()) + { + mLog.info("Removing idle recorder [" + entry.getKey() + "]"); + it.remove(); + entry.getValue().stop(); + } } } @@ -175,28 +237,18 @@ public class RecorderManager implements Listener } /** - * Monitors currently running recorders and removes/closes any recorders that are idle more than 6 seconds + * Processes queued audio packets and distributes to each of the audio recorders. Removes any idle recorders + * that have not been updated according to an idle threshold period */ - public class RecorderMonitor implements Runnable + public class BufferProcessor implements Runnable { @Override public void run() { synchronized(mRecorders) { - Iterator> it = mRecorders.entrySet().iterator(); - - while(it.hasNext()) - { - Map.Entry entry = it.next(); - - if(entry.getValue().getLastBufferReceived() + IDLE_RECORDER_REMOVAL_THRESHOLD < System.currentTimeMillis()) - { - mLog.info("Removing idle recorder [" + entry.getKey() + "]"); - it.remove(); - entry.getValue().stop(); - } - } + processBuffers(); + removeIdleRecorders(); } } } From ef172a04c6b6284a83276523532081efae5cd480 Mon Sep 17 00:00:00 2001 From: Denny Date: Fri, 3 Mar 2017 10:04:21 -0500 Subject: [PATCH 05/20] Cleanup - removed synchronized blocks since the buffer processor thread is the only thread managing recorders. --- src/record/RecorderManager.java | 82 +++++++++++++++------------------ 1 file changed, 38 insertions(+), 44 deletions(-) diff --git a/src/record/RecorderManager.java b/src/record/RecorderManager.java index cb30aa08..4d9789e0 100644 --- a/src/record/RecorderManager.java +++ b/src/record/RecorderManager.java @@ -60,9 +60,6 @@ public class RecorderManager implements Listener */ public RecorderManager() { - mBufferProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new BufferProcessor(), 0, - 1, TimeUnit.SECONDS); - mAudioPacketQueue.setOverflowListener(new IOverflowListener() { @Override @@ -78,6 +75,9 @@ public class RecorderManager implements Listener } } }); + + mBufferProcessorFuture = ThreadPool.SCHEDULED.scheduleAtFixedRate(new BufferProcessor(), 0, + 1, TimeUnit.SECONDS); } /** @@ -119,50 +119,47 @@ public class RecorderManager implements Listener { String identifier = audioPacket.getMetadata().getUniqueIdentifier(); - synchronized(mRecorders) + if(mRecorders.containsKey(identifier)) { - if(mRecorders.containsKey(identifier)) - { - RealBufferWaveRecorder recorder = mRecorders.get(identifier); + RealBufferWaveRecorder recorder = mRecorders.get(identifier); - if(audioPacket.getType() == AudioPacket.Type.AUDIO) - { - recorder.receive(audioPacket.getAudioBuffer()); - } - else if(audioPacket.getType() == AudioPacket.Type.END) - { - RealBufferWaveRecorder finished = mRecorders.remove(identifier); - finished.stop(); - } + if(audioPacket.getType() == AudioPacket.Type.AUDIO) + { + recorder.receive(audioPacket.getAudioBuffer()); } - else if(audioPacket.getType() == AudioPacket.Type.AUDIO) + else if(audioPacket.getType() == AudioPacket.Type.END) { - if(mCanStartNewRecorders) + RealBufferWaveRecorder finished = mRecorders.remove(identifier); + finished.stop(); + } + } + else if(audioPacket.getType() == AudioPacket.Type.AUDIO) + { + if(mCanStartNewRecorders) + { + String filePrefix = getFilePrefix(audioPacket); + + RealBufferWaveRecorder recorder = null; + + try { - String filePrefix = getFilePrefix(audioPacket); + recorder = new RealBufferWaveRecorder(AUDIO_SAMPLE_RATE, filePrefix); - RealBufferWaveRecorder recorder = null; + recorder.start(ThreadPool.SCHEDULED); - try + recorder.receive(audioPacket.getAudioBuffer()); + mRecorders.put(identifier, recorder); + } + catch(Exception ioe) + { + mCanStartNewRecorders = false; + + mLog.error("Error attempting to start new audio wave recorder. All (future) audio recording " + + "is disabled", ioe); + + if(recorder != null) { - recorder = new RealBufferWaveRecorder(AUDIO_SAMPLE_RATE, filePrefix); - - recorder.start(ThreadPool.SCHEDULED); - - recorder.receive(audioPacket.getAudioBuffer()); - mRecorders.put(identifier, recorder); - } - catch(Exception ioe) - { - mCanStartNewRecorders = false; - - mLog.error("Error attempting to start new audio wave recorder. All (future) audio recording " + - "is disabled", ioe); - - if(recorder != null) - { - recorder.stop(); - } + recorder.stop(); } } } @@ -245,11 +242,8 @@ public class RecorderManager implements Listener @Override public void run() { - synchronized(mRecorders) - { - processBuffers(); - removeIdleRecorders(); - } + processBuffers(); + removeIdleRecorders(); } } } From 61bfef5798a866c5dcc7ed7adccfa32b9e57c7f1 Mon Sep 17 00:00:00 2001 From: Denny Date: Fri, 3 Mar 2017 10:44:50 -0500 Subject: [PATCH 06/20] Incrementing build to version 0.3.0 beta8 --- build/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.xml b/build/build.xml index 6105114e..d3dd6483 100644 --- a/build/build.xml +++ b/build/build.xml @@ -7,7 +7,7 @@ - + From 57f665582479e7281e4ce84fd563b9dd7074bf8b Mon Sep 17 00:00:00 2001 From: Denny Date: Sun, 5 Mar 2017 07:32:42 -0500 Subject: [PATCH 07/20] Resolves issue where higher priority audio was not preempting lower priority audio. --- src/audio/AudioManager.java | 86 +++++++++++++++++++++++++------------ 1 file changed, 59 insertions(+), 27 deletions(-) diff --git a/src/audio/AudioManager.java b/src/audio/AudioManager.java index 2cab2400..4d6c57ac 100644 --- a/src/audio/AudioManager.java +++ b/src/audio/AudioManager.java @@ -86,6 +86,9 @@ public class AudioManager implements Listener, IAudioController loadSettings(); } + /** + * Loads the saved mixer configuration or a default configuration for audio playback. + */ private void loadSettings() { MixerChannelConfiguration configuration = null; @@ -132,6 +135,9 @@ public class AudioManager implements Listener, IAudioController } } + /** + * Creates a default audio playback configuration with a mono audio playback channel. + */ private MixerChannelConfiguration getDefaultConfiguration() { /* Use the system default mixer and mono channel as default startup */ @@ -161,6 +167,9 @@ public class AudioManager implements Listener, IAudioController mAudioOutputConnections.clear(); } + /** + * Primary ingest point for audio produced by all decoding channels, for distribution to audio playback devices. + */ @Override public synchronized void receive(AudioPacket packet) { @@ -176,11 +185,11 @@ public class AudioManager implements Listener, IAudioController for(AudioOutputConnection connection : mAudioOutputConnections) { - if(connection.isInactive() && - mChannelConnectionMap.containsKey(connection.getChannelMetadataID())) + if(connection.isInactive() && mChannelConnectionMap.containsKey(connection.getChannelMetadataID())) { mChannelConnectionMap.remove(connection.getChannelMetadataID()); connection.disconnect(); + mAvailableConnectionCount++; changed = true; } } @@ -192,7 +201,7 @@ public class AudioManager implements Listener, IAudioController } /** - * Identifies the lowest priority channel connection + * Identifies the lowest priority channel connection where the a higher value indicates a lower priority. */ private void updateLowestPriorityAssignment() { @@ -201,19 +210,32 @@ public class AudioManager implements Listener, IAudioController for(AudioOutputConnection connection : mAudioOutputConnections) { if(connection.isConnected() && - (mLowestPriorityConnection == null || mLowestPriorityConnection.getPriority() > connection.getPriority())) + (mLowestPriorityConnection == null || mLowestPriorityConnection.getPriority() < connection.getPriority())) { mLowestPriorityConnection = connection; } } } + /** + * Configures audio playback to use the configuration specified in the entry argument. + * + * @param entry to use in configuring the audio playback setup. + * @throws AudioException if there is an error + */ @Override public void setMixerChannelConfiguration(MixerChannelConfiguration entry) throws AudioException { setMixerChannelConfiguration(entry, true); } + /** + * Configures audio playback to use the configuration specified in the entry argument. + * + * @param entry to use in configuring the audio playback setup. + * @param saveSettings to save the audio playback configuration settings in the properties file. + * @throws AudioException if there is an error + */ public void setMixerChannelConfiguration(MixerChannelConfiguration entry, boolean saveSettings) throws AudioException { if(entry != null && (entry.getMixerChannel() == MixerChannel.MONO || entry.getMixerChannel() == MixerChannel.STEREO)) @@ -266,9 +288,8 @@ public class AudioManager implements Listener, IAudioController } /** - * Clears all channel assignments and terminates all audio outputs in - * preparation for complete shutdown or change to another mixer/channel - * configuration + * Clears all channel assignments and terminates all audio outputs in preparation for complete shutdown or change + * to another mixer/channel configuration */ private void disposeCurrentConfiguration() { @@ -288,12 +309,18 @@ public class AudioManager implements Listener, IAudioController mLowestPriorityConnection = null; } + /** + * Current audio playback mixer channel configuration setting. + */ @Override public MixerChannelConfiguration getMixerChannelConfiguration() throws AudioException { return mMixerChannelConfiguration; } + /** + * List of audio outputs available for the current mixer channel configuration + */ @Override public List getAudioOutputs() { @@ -311,12 +338,18 @@ public class AudioManager implements Listener, IAudioController return outputs; } + /** + * Adds an audio event listener to receive audio event notifications. + */ @Override public void addControllerListener(Listener listener) { mControllerBroadcaster.addListener(listener); } + /** + * Removes an audio event listener from receiving audio event notifications. + */ @Override public void removeControllerListener(Listener listener) { @@ -324,16 +357,15 @@ public class AudioManager implements Listener, IAudioController } /** - * Returns an audio output connection for the packet if one is - * available, or overrides an existing lower priority connection. - * Returns null if no connection is available for the audio packet. + * Returns an audio output connection for the packet if one is available, or overrides an existing lower priority + * connection. Returns null if no connection is available for the audio packet. * - * @param packet from a source + * @param audioPacket from a decoding channel source * @return an audio output connection or null */ - private AudioOutputConnection getConnection(AudioPacket packet) + private AudioOutputConnection getConnection(AudioPacket audioPacket) { - int channelMetadataID = packet.getMetadata().getMetadataID(); + int channelMetadataID = audioPacket.getMetadata().getMetadataID(); //Use an existing connection if(mChannelConnectionMap.containsKey(channelMetadataID)) @@ -341,37 +373,35 @@ public class AudioManager implements Listener, IAudioController return mChannelConnectionMap.get(channelMetadataID); } - //Connect to and use an available connection + //Connect to an unused, available connection if(mAvailableConnectionCount > 0) { for(AudioOutputConnection connection : mAudioOutputConnections) { if(connection.isDisconnected()) { - connection.connect(channelMetadataID); + connection.connect(channelMetadataID, audioPacket.getMetadata().getAudioPriority()); mChannelConnectionMap.put(channelMetadataID, connection); + mAvailableConnectionCount--; return connection; } } } - //Preempt an existing connection and connect it to this higher priority source - else //Check for a channel priority override + //Preempt an existing lower priority connection and connect when this is a higher priority packet + else { - int priority = packet.getMetadata().getAudioPriority(); + int priority = audioPacket.getMetadata().getAudioPriority(); - if(mLowestPriorityConnection != null && - priority < mLowestPriorityConnection.getPriority()) + AudioOutputConnection connection = mLowestPriorityConnection; + + if(connection != null && priority < connection.getPriority()) { - AudioOutputConnection connection = mLowestPriorityConnection; - mChannelConnectionMap.remove(connection.getChannelMetadataID()); - connection.connect(channelMetadataID); + connection.connect(channelMetadataID, priority); mChannelConnectionMap.put(channelMetadataID, connection); - updateLowestPriorityAssignment(); - return connection; } } @@ -396,7 +426,7 @@ public class AudioManager implements Listener, IAudioController for(AudioPacket packet : packets) { - /* Don't process any packet's marked as do not monitor */ + /* Don't process any packet's marked as do not monitor */ if(!packet.getMetadata().isDoNotMonitor() && packet.getType() == AudioPacket.Type.AUDIO) { AudioOutputConnection connection = getConnection(packet); @@ -494,9 +524,11 @@ public class AudioManager implements Listener, IAudioController * Connects this assignment to the indicated source so that audio * packets from this source can be sent to the audio output */ - public void connect(int source) + public void connect(int source, int priority) { mChannelMetadataID = source; + mPriority = priority; + updateLowestPriorityAssignment(); mAudioOutput.updateTimestamp(); } From 1db1d2839083f5553560741e36132b2141aa0da3 Mon Sep 17 00:00:00 2001 From: Denny Date: Sat, 11 Mar 2017 06:13:59 -0500 Subject: [PATCH 08/20] Resolves issue with frequency display formating in the Now Playing window. --- src/channel/metadata/ChannelMetadataModel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/channel/metadata/ChannelMetadataModel.java b/src/channel/metadata/ChannelMetadataModel.java index b5648eb3..4d6844af 100644 --- a/src/channel/metadata/ChannelMetadataModel.java +++ b/src/channel/metadata/ChannelMetadataModel.java @@ -32,7 +32,7 @@ import java.util.Map; public class ChannelMetadataModel extends AbstractTableModel implements Listener { - private final DecimalFormat FREQUENCY_FORMATTER = new DecimalFormat( "#.0000" ); + private final DecimalFormat FREQUENCY_FORMATTER = new DecimalFormat( "#.00000" ); public static final int COLUMN_STATE = 0; public static final int COLUMN_DECODER = 1; From 1aa85a86ca441dc765a9eb3c18f08f825ea17497 Mon Sep 17 00:00:00 2001 From: Denny Date: Sat, 11 Mar 2017 10:16:08 -0500 Subject: [PATCH 09/20] Resolves issue with channel frequency correction events not being broadcast within the processing chain and rendering in the spectral display. --- src/channel/state/ChannelState.java | 36 +- src/controller/channel/Channel.java | 807 ++++++++--------- .../channel/ChannelProcessingManager.java | 8 +- .../InstrumentableProcessingChain.java | 117 +-- src/module/ProcessingChain.java | 61 +- src/module/decode/p25/C4FMSymbolFilter.java | 28 +- src/module/decode/p25/P25Decoder.java | 4 +- src/module/decode/p25/P25MessageFramer.java | 3 +- src/module/decode/p25/P25_C4FMDecoder.java | 34 +- src/module/decode/p25/P25_LSMDecoder.java | 24 +- src/source/Source.java | 4 +- src/source/mixer/RealMixerSource.java | 39 +- src/source/tuner/TunerChannelSource.java | 214 ++++- .../frequency/IFrequencyChangeProvider.java | 25 +- src/source/wave/ComplexWaveSource.java | 55 +- src/source/wave/RealWaveSource.java | 24 +- src/spectrum/OverlayPanel.java | 40 +- src/spectrum/SpectrumPanel.java | 816 +++++++++--------- 18 files changed, 1336 insertions(+), 1003 deletions(-) diff --git a/src/channel/state/ChannelState.java b/src/channel/state/ChannelState.java index 768c1062..d9407c0f 100644 --- a/src/channel/state/ChannelState.java +++ b/src/channel/state/ChannelState.java @@ -36,14 +36,19 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sample.Listener; 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, ISquelchStateProvider, IAttributeChangeRequestListener, IOverflowListener + IDecoderStateEventProvider, IFrequencyChangeListener, ISquelchStateProvider, IAttributeChangeRequestListener, + IOverflowListener { private final static Logger mLog = LoggerFactory.getLogger(ChannelState.class); @@ -61,6 +66,7 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder private ChannelType mChannelType; private TrafficChannelManager mTrafficChannelEndListener; private CallEvent mTrafficChannelCallEvent; + private FrequencyChangeListener mFrequencyChangeListener; private boolean mSquelchLocked = false; private boolean mSelected = false; @@ -189,6 +195,17 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder mStateMonitor = null; } + @Override + public Listener getFrequencyChangeListener() + { + if(mFrequencyChangeListener == null) + { + mFrequencyChangeListener = new FrequencyChangeListener(); + } + + return mFrequencyChangeListener; + } + private boolean isStandardChannel() { return mChannelType == ChannelType.STANDARD; @@ -532,6 +549,23 @@ public class ChannelState extends Module implements ICallEventProvider, IDecoder broadcast(mTrafficChannelCallEvent); } + /** + * Listener to receive frequency change events and rebroadcast them to the decoder states + */ + public class FrequencyChangeListener implements Listener + { + @Override + public void receive(FrequencyChangeEvent frequencyChangeEvent) + { + if(frequencyChangeEvent.getEvent() == NOTIFICATION_FREQUENCY_CHANGE) + { + long frequency = frequencyChangeEvent.getValue().longValue(); + + broadcast(new DecoderStateEvent(this, Event.SOURCE_FREQUENCY, getState(), frequency)); + } + } + } + /** * DecoderStateEvent receiver wrapper */ diff --git a/src/controller/channel/Channel.java b/src/controller/channel/Channel.java index 81ced518..79be174b 100644 --- a/src/controller/channel/Channel.java +++ b/src/controller/channel/Channel.java @@ -1,28 +1,24 @@ /******************************************************************************* - * SDR Trunk - * Copyright (C) 2014-2016 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 + * 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 controller.channel; -import javax.xml.bind.annotation.XmlAttribute; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; -import javax.xml.bind.annotation.XmlSeeAlso; -import javax.xml.bind.annotation.XmlTransient; - +import controller.config.Configuration; import module.decode.DecoderFactory; import module.decode.DecoderType; import module.decode.config.AuxDecodeConfiguration; @@ -31,6 +27,7 @@ import module.log.EventLogType; import module.log.config.EventLogConfiguration; import record.RecorderType; import record.config.RecordConfiguration; +import sample.Listener; import source.SourceType; import source.config.SourceConfigFactory; import source.config.SourceConfigRecording; @@ -40,424 +37,444 @@ import source.tuner.TunerChannel; import source.tuner.TunerChannel.Type; import source.tuner.frequency.FrequencyChangeEvent; import source.tuner.frequency.FrequencyChangeEvent.Event; -import source.tuner.frequency.IFrequencyChangeProcessor; -import controller.config.Configuration; -@XmlSeeAlso( { Configuration.class } ) -@XmlRootElement( name = "channel" ) -public class Channel extends Configuration implements IFrequencyChangeProcessor +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlSeeAlso; +import javax.xml.bind.annotation.XmlTransient; + +@XmlSeeAlso({Configuration.class}) +@XmlRootElement(name = "channel") +public class Channel extends Configuration implements Listener { - // Standard channels are persisted and traffic channels are temporary - public enum ChannelType { STANDARD, TRAFFIC }; - + // Standard channels are persisted and traffic channels are temporary + public enum ChannelType + { + STANDARD, TRAFFIC + } - // Static unique channel identifier tracking - private static int UNIQUE_ID = 0; + ; - private DecodeConfiguration mDecodeConfiguration = DecoderFactory.getDefaultDecodeConfiguration(); - private AuxDecodeConfiguration mAuxDecodeConfiguration = - new AuxDecodeConfiguration(); - private SourceConfiguration mSourceConfiguration = - SourceConfigFactory.getDefaultSourceConfiguration(); - private EventLogConfiguration mEventLogConfiguration = - new EventLogConfiguration(); - private RecordConfiguration mRecordConfiguration = new RecordConfiguration(); - private String mAliasListName; - private String mSystem = "System"; + // Static unique channel identifier tracking + private static int UNIQUE_ID = 0; + + private DecodeConfiguration mDecodeConfiguration = DecoderFactory.getDefaultDecodeConfiguration(); + private AuxDecodeConfiguration mAuxDecodeConfiguration = + new AuxDecodeConfiguration(); + private SourceConfiguration mSourceConfiguration = + SourceConfigFactory.getDefaultSourceConfiguration(); + private EventLogConfiguration mEventLogConfiguration = + new EventLogConfiguration(); + private RecordConfiguration mRecordConfiguration = new RecordConfiguration(); + + private String mAliasListName; + private String mSystem = "System"; private String mSite = "Site"; private String mName = "Channel"; - - private boolean mEnabled; - private boolean mSelected; - private TunerChannel mTunerChannel = null; - - private ChannelType mChannelType = ChannelType.STANDARD; - private int mChannelID; - private int mChannelFrequencyCorrection = 0; - - /** - * Channel represents a complete set of configurations needed to setup and - * manage a processing chain and/or manage as a set of business objects that - * can be displayed in graphical components. - * - * @param channelName - * @param channelType - */ - public Channel( String channelName, ChannelType channelType ) - { - this( channelName ); - mChannelType = channelType; - } + private boolean mEnabled; + private boolean mSelected; + private TunerChannel mTunerChannel = null; - /** - * Constructs a new standard channel with the specified channel name - */ - public Channel( String channelName ) - { - this(); - mName = channelName; - } + private ChannelType mChannelType = ChannelType.STANDARD; - /** - * Constructs a new standard channel with a default name of "Channel" - */ - public Channel() - { - mChannelID = UNIQUE_ID++; - } + private int mChannelID; + private int mChannelFrequencyCorrection = 0; - /** - * Creates a (new) deep copy of this channel - */ - public Channel copyOf() - { - Channel channel = new Channel( mName ); - channel.setSystem( mSystem ); - channel.setSite( mSite ); - channel.setAliasListName( mAliasListName ); + /** + * Channel represents a complete set of configurations needed to setup and + * manage a processing chain and/or manage as a set of business objects that + * can be displayed in graphical components. + * + * @param channelName + * @param channelType + */ + public Channel(String channelName, ChannelType channelType) + { + this(channelName); + mChannelType = channelType; + } - AuxDecodeConfiguration aux = new AuxDecodeConfiguration(); - - for( DecoderType auxType: aux.getAuxDecoders() ) - { - aux.addAuxDecoder( auxType ); - } - - channel.setAuxDecodeConfiguration( aux ); - - channel.setDecodeConfiguration( DecoderFactory.copy( mDecodeConfiguration ) ); - - EventLogConfiguration log = new EventLogConfiguration(); - - for( EventLogType logType: mEventLogConfiguration.getLoggers() ) - { - log.addLogger( logType ); - } - - channel.setEventLogConfiguration( log ); + /** + * Constructs a new standard channel with the specified channel name + */ + public Channel(String channelName) + { + this(); + mName = channelName; + } - RecordConfiguration record = new RecordConfiguration(); - - for( RecorderType recordType: mRecordConfiguration.getRecorders() ) - { - record.addRecorder( recordType ); - } - - channel.setRecordConfiguration( record ); + /** + * Constructs a new standard channel with a default name of "Channel" + */ + public Channel() + { + mChannelID = UNIQUE_ID++; + } + + /** + * Creates a (new) deep copy of this channel + */ + public Channel copyOf() + { + Channel channel = new Channel(mName); + channel.setSystem(mSystem); + channel.setSite(mSite); + channel.setAliasListName(mAliasListName); + + AuxDecodeConfiguration aux = new AuxDecodeConfiguration(); + + for(DecoderType auxType : aux.getAuxDecoders()) + { + aux.addAuxDecoder(auxType); + } + + channel.setAuxDecodeConfiguration(aux); + + channel.setDecodeConfiguration(DecoderFactory.copy(mDecodeConfiguration)); + + EventLogConfiguration log = new EventLogConfiguration(); + + for(EventLogType logType : mEventLogConfiguration.getLoggers()) + { + log.addLogger(logType); + } + + channel.setEventLogConfiguration(log); + + RecordConfiguration record = new RecordConfiguration(); + + for(RecorderType recordType : mRecordConfiguration.getRecorders()) + { + record.addRecorder(recordType); + } + + channel.setRecordConfiguration(record); + + channel.setSourceConfiguration(SourceConfigFactory.copy(mSourceConfiguration)); + + return channel; + } + + /** + * Unique identifier for this channel. Value is transient (not persisted). + */ + @XmlTransient + public int getChannelID() + { + return mChannelID; + } + + /** + * Indicates if this channel has been selected for prioritized audio output + * and display of processing chain products + */ + @XmlTransient + public boolean isSelected() + { + return mSelected; + } + + /** + * Sets selection status of the channel. + * + * NOTE: this method is package private so that only the ChannelSelectionManager + * can toggle the selection state. Use the channel model to broadcast a + * request select/deselect channel event. + * + * @param selected + */ + void setSelected(boolean selected) + { + mSelected = selected; + } + + @XmlTransient + public ChannelType getChannelType() + { + return mChannelType; + } - channel.setSourceConfiguration( SourceConfigFactory.copy( mSourceConfiguration ) ); - - return channel; - } - - /** - * Unique identifier for this channel. Value is transient (not persisted). - */ - @XmlTransient - public int getChannelID() - { - return mChannelID; - } - - /** - * Indicates if this channel has been selected for prioritized audio output - * and display of processing chain products - */ - @XmlTransient - public boolean isSelected() - { - return mSelected; - } - - /** - * Sets selection status of the channel. - * - * NOTE: this method is package private so that only the ChannelSelectionManager - * can toggle the selection state. Use the channel model to broadcast a - * request select/deselect channel event. - * - * @param selected - */ - void setSelected( boolean selected ) - { - mSelected = selected; - } - - @XmlTransient - public ChannelType getChannelType() - { - return mChannelType; - } - /** * Returns the owning system for this channel. */ - @XmlAttribute( name = "system" ) - public String getSystem() - { - return mSystem; - } - - public void setSystem( String system ) - { - mSystem = system; - } + @XmlAttribute(name = "system") + public String getSystem() + { + return mSystem; + } - public boolean hasSystem() - { - return mSystem != null; - } - - /** - * Returns the owning site for this channel. - */ - @XmlAttribute( name = "site" ) - public String getSite() - { - return mSite; - } - - public void setSite( String site ) - { - mSite = site; - } - - public boolean hasSite() - { - return mSite != null; - } - - /** - * Returns the name of this channel. - */ - @XmlAttribute( name = "name" ) - public String getName() - { - return mName; - } - - /** - * Sets the name of this channel. - */ - public void setName( String name ) - { - mName = name; - } + public void setSystem(String system) + { + mSystem = system; + } + + public boolean hasSystem() + { + return mSystem != null; + } + + /** + * Returns the owning site for this channel. + */ + @XmlAttribute(name = "site") + public String getSite() + { + return mSite; + } + + public void setSite(String site) + { + mSite = site; + } + + public boolean hasSite() + { + return mSite != null; + } + + /** + * Returns the name of this channel. + */ + @XmlAttribute(name = "name") + public String getName() + { + return mName; + } + + /** + * Sets the name of this channel. + */ + public void setName(String name) + { + mName = name; + } /** * Default display string for this channel: SYSTEM_SITE_NAME */ - public String toString() - { - StringBuilder sb = new StringBuilder(); - sb.append( hasSystem() ? getSystem() : "SYSTEM" ); - sb.append( "_" ); - sb.append( hasSite() ? getSite() : "SITE" ); - sb.append( "_" ); - sb.append( getName() ); - - return sb.toString(); - } + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append(hasSystem() ? getSystem() : "SYSTEM"); + sb.append("_"); + sb.append(hasSite() ? getSite() : "SITE"); + sb.append("_"); + sb.append(getName()); - /** - * Indicates if this channel is enabled for processing. - */ - @XmlAttribute( name = "enabled" ) - public boolean getEnabled() - { - return mEnabled; - } + return sb.toString(); + } - /** - * Indicates if this channel will automatically start processing on - * application startup or if this channel is currently processing after - * application startup. - * - * Note: this method is package private and is intended to be managed - * by a co-package central processing manager - * - * @see controller.ChannelProcessingManager - */ - void setEnabled( boolean enabled ) - { - mEnabled = enabled; - } + /** + * Indicates if this channel is enabled for processing. + */ + @XmlAttribute(name = "enabled") + public boolean getEnabled() + { + return mEnabled; + } - /** - * Returns the alias list that is used by this channel for looking up alias - * values for the various identifiers produced by the decoder(s). - */ - @XmlElement( name = "alias_list_name" ) - public String getAliasListName() - { - return mAliasListName; - } + /** + * Indicates if this channel will automatically start processing on + * application startup or if this channel is currently processing after + * application startup. + * + * Note: this method is package private and is intended to be managed + * by a co-package central processing manager + * + * @see controller.channel.ChannelProcessingManager + */ + void setEnabled(boolean enabled) + { + mEnabled = enabled; + } - /** - * Sets the alias list to be used for looking up the alias values for the - * various identifiers produced by the decoder - */ - public void setAliasListName( String name ) - { - mAliasListName = name; - } + /** + * Returns the alias list that is used by this channel for looking up alias + * values for the various identifiers produced by the decoder(s). + */ + @XmlElement(name = "alias_list_name") + public String getAliasListName() + { + return mAliasListName; + } - /** - * Gets the primary decoder configuration used by this channel - */ - @XmlElement( name = "decode_configuration" ) - public DecodeConfiguration getDecodeConfiguration() - { - return mDecodeConfiguration; - } + /** + * Sets the alias list to be used for looking up the alias values for the + * various identifiers produced by the decoder + */ + public void setAliasListName(String name) + { + mAliasListName = name; + } - /** - * Sets the primary decoder configuration used by this channel - */ - public void setDecodeConfiguration( DecodeConfiguration config ) - { - mDecodeConfiguration = config; - } + /** + * Gets the primary decoder configuration used by this channel + */ + @XmlElement(name = "decode_configuration") + public DecodeConfiguration getDecodeConfiguration() + { + return mDecodeConfiguration; + } - /** - * Gets the aux decoder configuration used by this channel. Aux decoders - * operate on demodulated audio - */ - @XmlElement( name = "aux_decode_configuration" ) - public AuxDecodeConfiguration getAuxDecodeConfiguration() - { - return mAuxDecodeConfiguration; - } + /** + * Sets the primary decoder configuration used by this channel + */ + public void setDecodeConfiguration(DecodeConfiguration config) + { + mDecodeConfiguration = config; + } - /** - * Sets the decoder configuration used by this channel - */ - public void setAuxDecodeConfiguration( AuxDecodeConfiguration config ) - { - mAuxDecodeConfiguration = config; - } + /** + * Gets the aux decoder configuration used by this channel. Aux decoders + * operate on demodulated audio + */ + @XmlElement(name = "aux_decode_configuration") + public AuxDecodeConfiguration getAuxDecodeConfiguration() + { + return mAuxDecodeConfiguration; + } - /** - * Returns the source configuration for this channel. A channel source - * identifies where the processing chain will get source sample data from. - */ - @XmlElement( name = "source_configuration" ) - public SourceConfiguration getSourceConfiguration() - { - return mSourceConfiguration; - } + /** + * Sets the decoder configuration used by this channel + */ + public void setAuxDecodeConfiguration(AuxDecodeConfiguration config) + { + mAuxDecodeConfiguration = config; + } - /** - * Sets the source configuration for this channel. - */ - public void setSourceConfiguration( SourceConfiguration config ) - { - mSourceConfiguration = config; + /** + * Returns the source configuration for this channel. A channel source + * identifies where the processing chain will get source sample data from. + */ + @XmlElement(name = "source_configuration") + public SourceConfiguration getSourceConfiguration() + { + return mSourceConfiguration; + } - //Clear the tune channel object so that it can be recreated if the - //source configuration changes - mTunerChannel = null; - } + /** + * Sets the source configuration for this channel. + */ + public void setSourceConfiguration(SourceConfiguration config) + { + mSourceConfiguration = config; - /** - * Returns the event logger configuration for this channel. - */ - @XmlElement( name = "event_log_configuration" ) - public EventLogConfiguration getEventLogConfiguration() - { - return mEventLogConfiguration; - } + //Clear the tune channel object so that it can be recreated if the + //source configuration changes + mTunerChannel = null; + } - /** - * Sets the event logger configuration for this channel. - */ - public void setEventLogConfiguration( EventLogConfiguration config ) - { - mEventLogConfiguration = config; - } + /** + * Returns the event logger configuration for this channel. + */ + @XmlElement(name = "event_log_configuration") + public EventLogConfiguration getEventLogConfiguration() + { + return mEventLogConfiguration; + } - /** - * Returns the recorder configuration for this channel. - */ - @XmlElement( name = "record_configuration" ) - public RecordConfiguration getRecordConfiguration() - { - return mRecordConfiguration; - } + /** + * Sets the event logger configuration for this channel. + */ + public void setEventLogConfiguration(EventLogConfiguration config) + { + mEventLogConfiguration = config; + } - /** - * Sets the recorder configuration for this channel. - */ - public void setRecordConfiguration( RecordConfiguration config ) - { - mRecordConfiguration = config; - } + /** + * Returns the recorder configuration for this channel. + */ + @XmlElement(name = "record_configuration") + public RecordConfiguration getRecordConfiguration() + { + return mRecordConfiguration; + } - /** - * Convenience method to construct a tuner channel object representing a - * tuner or recording source frequency and bandwidth that can be used by - * application components for graphically representing this channel. - * - * If the source configuration is not a tuner or recording, this method - * returns null. - */ - @XmlTransient + /** + * Sets the recorder configuration for this channel. + */ + public void setRecordConfiguration(RecordConfiguration config) + { + mRecordConfiguration = config; + } + + /** + * Convenience method to construct a tuner channel object representing a + * tuner or recording source frequency and bandwidth that can be used by + * application components for graphically representing this channel. + * + * If the source configuration is not a tuner or recording, this method + * returns null. + */ + @XmlTransient public TunerChannel getTunerChannel() { - if( mTunerChannel == null ) - { - if( mSourceConfiguration.getSourceType() == SourceType.TUNER ) - { - SourceConfigTuner config = (SourceConfigTuner)mSourceConfiguration; + if(mTunerChannel == null) + { + if(mSourceConfiguration.getSourceType() == SourceType.TUNER) + { + SourceConfigTuner config = (SourceConfigTuner)mSourceConfiguration; - mTunerChannel = new TunerChannel( Type.LOCKED, config.getFrequency(), - mDecodeConfiguration.getDecoderType().getChannelBandwidth() ); - } - else if( mSourceConfiguration.getSourceType() == SourceType.RECORDING ) - { - SourceConfigRecording config = - (SourceConfigRecording)mSourceConfiguration; + mTunerChannel = new TunerChannel(Type.LOCKED, config.getFrequency(), + mDecodeConfiguration.getDecoderType().getChannelBandwidth()); + } + else if(mSourceConfiguration.getSourceType() == SourceType.RECORDING) + { + SourceConfigRecording config = + (SourceConfigRecording)mSourceConfiguration; + + mTunerChannel = new TunerChannel(Type.LOCKED, config.getFrequency(), + mDecodeConfiguration.getDecoderType().getChannelBandwidth()); + } + } - mTunerChannel = new TunerChannel( Type.LOCKED, config.getFrequency(), - mDecodeConfiguration.getDecoderType().getChannelBandwidth() ); - } - } - return mTunerChannel; } - /** - * Convenience method to indicate if any part of this channel is contained - * within the minimum and maximum frequency values. - */ - public boolean isWithin( long minimum, long maximum ) - { - TunerChannel tunerChannel = getTunerChannel(); - - return tunerChannel != null && - tunerChannel.overlaps( minimum, maximum ); - } - - /** - * Convenience method to make the current channel frequency correction value - * available for use outside of a constructed processing chain while maintaining - * linkage to the source channel. This hack allows the correction value to - * be used to visually show in the spectral display. - */ - @Override - public void frequencyChanged( FrequencyChangeEvent event ) - { - if( event.getEvent() == Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE ) - { - mChannelFrequencyCorrection = event.getValue().intValue(); - } - } + /** + * Convenience method to indicate if any part of this channel is contained + * within the minimum and maximum frequency values. + */ + public boolean isWithin(long minimum, long maximum) + { + TunerChannel tunerChannel = getTunerChannel(); - @XmlTransient - public int getChannelFrequencyCorrection() - { - return mChannelFrequencyCorrection; - } + return tunerChannel != null && tunerChannel.overlaps(minimum, maximum); + } + + /** + * Convenience method to make the current channel frequency correction value + * available for use outside of a constructed processing chain while maintaining + * linkage to the source channel. This hack allows the correction value to + * be used to visually show in the spectral display. + */ + @Override + public void receive(FrequencyChangeEvent event) + { + if(event.getEvent() == Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE) + { + mChannelFrequencyCorrection = event.getValue().intValue(); + } + } + + /** + * Current channel frequency correction value (when this channel is processing) + * @return + */ + @XmlTransient + public int getChannelFrequencyCorrection() + { + return mChannelFrequencyCorrection; + } + + /** + * Resets frequency correction value to 0 + */ + public void resetFrequencyCorrection() + { + mChannelFrequencyCorrection = 0; + } } diff --git a/src/controller/channel/ChannelProcessingManager.java b/src/controller/channel/ChannelProcessingManager.java index 93bf1f0c..ad65bd3c 100644 --- a/src/controller/channel/ChannelProcessingManager.java +++ b/src/controller/channel/ChannelProcessingManager.java @@ -231,7 +231,7 @@ public class ChannelProcessingManager implements ChannelEventListener if(processingChain == null) { - processingChain = new ProcessingChain(channel.getName(), channel.getChannelType()); + processingChain = new ProcessingChain(channel.getChannelType()); /* Register global listeners */ for(Listener listener : mAudioPacketListeners) @@ -244,6 +244,9 @@ public class ChannelProcessingManager implements ChannelEventListener processingChain.addMessageListener(listener); } + //Register channel to receive frequency correction events to show in the spectral display (hack!) + processingChain.addFrequencyChangeListener(channel); + /* Processing Modules */ List modules = DecoderFactory.getModules(mChannelModel, mChannelMapModel, this, mAliasModel, channel, processingChain.getChannelState().getMutableMetadata()); @@ -347,6 +350,9 @@ public class ChannelProcessingManager implements ChannelEventListener processingChain.removeRecordingModules(); + //Deregister channel from receive frequency correction events to show in the spectral display (hack!) + processingChain.removeFrequencyChangeListener(channel); + channel.resetFrequencyCorrection(); mChannelModel.broadcast(new ChannelEvent(channel, Event.NOTIFICATION_PROCESSING_STOP)); diff --git a/src/instrument/InstrumentableProcessingChain.java b/src/instrument/InstrumentableProcessingChain.java index 882eb891..0ca61c90 100644 --- a/src/instrument/InstrumentableProcessingChain.java +++ b/src/instrument/InstrumentableProcessingChain.java @@ -1,65 +1,82 @@ +/******************************************************************************* + * 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 instrument; +import controller.channel.Channel.ChannelType; import instrument.tap.Tap; import instrument.tap.TapGroup; +import module.Module; +import module.ProcessingChain; +import source.Source; import java.util.ArrayList; import java.util.List; -import module.Module; -import module.ProcessingChain; -import source.Source; -import controller.channel.Channel.ChannelType; - public class InstrumentableProcessingChain extends ProcessingChain implements Instrumentable { - public InstrumentableProcessingChain() - { - super( "instrumented", ChannelType.STANDARD ); - } + public InstrumentableProcessingChain() + { + super(ChannelType.STANDARD); + } - public void setSource( Source source ) throws IllegalStateException - { - mSource = source; - } + public void setSource(Source source) throws IllegalStateException + { + mSource = source; + } - @Override - public List getTapGroups() - { - List groups = new ArrayList<>(); - - for( Module module: getModules() ) - { - if( module instanceof Instrumentable ) - { - groups.addAll( ((Instrumentable)module).getTapGroups() ); - } - } - - return groups; - } + @Override + public List getTapGroups() + { + List groups = new ArrayList<>(); - @Override - public void registerTap( Tap tap ) - { - for( Module module: getModules() ) - { - if( module instanceof Instrumentable ) - { - ((Instrumentable)module).registerTap( tap ); - } - } - } + for(Module module : getModules()) + { + if(module instanceof Instrumentable) + { + groups.addAll(((Instrumentable)module).getTapGroups()); + } + } - @Override - public void unregisterTap( Tap tap ) - { - for( Module module: getModules() ) - { - if( module instanceof Instrumentable ) - { - ((Instrumentable)module).unregisterTap( tap ); - } - } - } + return groups; + } + + @Override + public void registerTap(Tap tap) + { + for(Module module : getModules()) + { + if(module instanceof Instrumentable) + { + ((Instrumentable)module).registerTap(tap); + } + } + } + + @Override + public void unregisterTap(Tap tap) + { + for(Module module : getModules()) + { + if(module instanceof Instrumentable) + { + ((Instrumentable)module).unregisterTap(tap); + } + } + } } diff --git a/src/module/ProcessingChain.java b/src/module/ProcessingChain.java index fdf60e86..a018e360 100644 --- a/src/module/ProcessingChain.java +++ b/src/module/ProcessingChain.java @@ -108,7 +108,6 @@ public class ProcessingChain implements IChannelEventListener private Broadcaster mUnFilteredRealBufferBroadcaster = new Broadcaster<>(); private Broadcaster mSquelchStateBroadcaster = new Broadcaster<>(); - private String mName; private AtomicBoolean mRunning = new AtomicBoolean(); protected Source mSource; @@ -120,13 +119,10 @@ public class ProcessingChain implements IChannelEventListener /** * Creates a processing chain for managing a set of modules * - * @param name for thread pool naming each thread * @param channelType */ - public ProcessingChain(String name, ChannelType channelType) + public ProcessingChain(ChannelType channelType) { - mName = name; - mChannelState = new ChannelState(channelType); addModule(mChannelState); @@ -211,6 +207,8 @@ public class ProcessingChain implements IChannelEventListener } mSource = source; + + addModule(mSource); } /** @@ -557,25 +555,6 @@ public class ProcessingChain implements IChannelEventListener + "sample type - cannot start processing chain"); } - /* If this is a tuner source, broadcast the frequency to all - * of the decoder state's and start the samples flowing */ - if(mSource instanceof TunerChannelSource) - { - TunerChannelSource tcs = (TunerChannelSource) mSource; - - try - { - long frequency = tcs.getFrequency(); - - mDecoderStateEventBroadcaster.broadcast( - new DecoderStateEvent(this, Event.SOURCE_FREQUENCY, State.IDLE, frequency)); - } - catch(SourceException e) - { - mLog.error("Error getting frequency from tuner channel source", e); - } - } - /* Start each of the modules */ for(Module module : mModules) { @@ -588,8 +567,6 @@ public class ProcessingChain implements IChannelEventListener mLog.error("Error starting module", e); } } - - mSource.start(ThreadPool.SCHEDULED); } else { @@ -606,9 +583,16 @@ public class ProcessingChain implements IChannelEventListener { if(mRunning.compareAndSet(true, false)) { + /* Stop each of the modules */ + for(Module module : mModules) + { + module.stop(); + } + if(mSource != null) { - mSource.stop(); + removeModule(mSource); + mSource.setOverflowListener(null); switch(mSource.getSampleType()) @@ -629,11 +613,6 @@ public class ProcessingChain implements IChannelEventListener mSource = null; } - /* Stop each of the modules */ - for(Module module : mModules) - { - module.stop(); - } } } @@ -760,6 +739,24 @@ public class ProcessingChain implements IChannelEventListener mMessageBroadcaster.removeListener(listener); } + /** + * Adds the listener to receive frequency change events from the processing chain + * @param listener to receive events + */ + public void addFrequencyChangeListener(Listener listener) + { + mFrequencyChangeEventBroadcaster.addListener(listener); + } + + /** + * Removes the listener from receiving frequency change events + * @param listener to remove + */ + public void removeFrequencyChangeListener(Listener listener) + { + mFrequencyChangeEventBroadcaster.removeListener(listener); + } + /** * Adds the listener to receive call events from all modules. */ diff --git a/src/module/decode/p25/C4FMSymbolFilter.java b/src/module/decode/p25/C4FMSymbolFilter.java index 9b91c6e0..51e7b78d 100644 --- a/src/module/decode/p25/C4FMSymbolFilter.java +++ b/src/module/decode/p25/C4FMSymbolFilter.java @@ -9,21 +9,21 @@ import instrument.tap.stream.FloatTap; import java.util.ArrayList; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import sample.Listener; import sample.real.RealBuffer; import sample.real.RealSampleListener; import sample.real.RealSampleProvider; +import source.tuner.TunerChannelSource; import source.tuner.frequency.FrequencyChangeEvent; import source.tuner.frequency.FrequencyChangeEvent.Event; import source.tuner.frequency.IFrequencyChangeListener; import dsp.gain.DirectGainControl; -public class C4FMSymbolFilter implements Listener, - IFrequencyChangeListener, - RealSampleProvider, - Instrumentable +public class C4FMSymbolFilter implements Listener, IFrequencyChangeListener, RealSampleProvider, Instrumentable { - private static final float TAPS[][] = + private static final float TAPS[][] = { { 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f, 1.00000e+00f, 0.00000e+00f, 0.00000e+00f, 0.00000e+00f }, // 0/128 { -1.54700e-04f, 8.53777e-04f, -2.76968e-03f, 7.89295e-03f, 9.98534e-01f, -5.41054e-03f, 1.24642e-03f, -1.98993e-04f }, // 1/128 @@ -198,8 +198,7 @@ public class C4FMSymbolFilter implements Listener, private RealSampleListener mListener; - private DirectGainControl mGainController = - new DirectGainControl( 15.0f, 0.1f, 35.0f, 0.3f ); + private DirectGainControl mGainController = new DirectGainControl( 15.0f, 0.1f, 35.0f, 0.3f ); private int mFrequencyAdjustmentRequested = 0; private int mFrequencyCorrection = 0; @@ -258,9 +257,8 @@ public class C4FMSymbolFilter implements Listener, correction = -mFrequencyCorrectionMaximum; } - broadcast( new FrequencyChangeEvent( - Event.REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE, correction ) ); - + broadcast( new FrequencyChangeEvent(Event.REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE, correction ) ); + mFrequencyAdjustmentRequested = 0; } } @@ -452,6 +450,10 @@ public class C4FMSymbolFilter implements Listener, } } + /** + * Broadcasts a frequency change event to the registered listener + * @param event + */ public void broadcast( FrequencyChangeEvent event ) { if( mFrequencyChangeListener != null ) @@ -459,7 +461,11 @@ public class C4FMSymbolFilter implements Listener, mFrequencyChangeListener.receive( event ); } } - + + /** + * Sets the listener for frequency change events. + * @param listener + */ public void setFrequencyChangeListener( Listener listener ) { mFrequencyChangeListener = listener; diff --git a/src/module/decode/p25/P25Decoder.java b/src/module/decode/p25/P25Decoder.java index 184c6f88..0ca1f8a9 100644 --- a/src/module/decode/p25/P25Decoder.java +++ b/src/module/decode/p25/P25Decoder.java @@ -4,8 +4,10 @@ import alias.AliasList; import instrument.Instrumentable; import module.decode.Decoder; import module.decode.DecoderType; +import source.tuner.frequency.IFrequencyChangeListener; +import source.tuner.frequency.IFrequencyChangeProvider; -public abstract class P25Decoder extends Decoder implements Instrumentable +public abstract class P25Decoder extends Decoder implements IFrequencyChangeListener, IFrequencyChangeProvider, Instrumentable { private P25MessageProcessor mMessageProcessor; diff --git a/src/module/decode/p25/P25MessageFramer.java b/src/module/decode/p25/P25MessageFramer.java index 39d9bb80..30af300b 100644 --- a/src/module/decode/p25/P25MessageFramer.java +++ b/src/module/decode/p25/P25MessageFramer.java @@ -689,8 +689,7 @@ public class P25MessageFramer implements Listener private LSMDemodulator mDemodulator; private double mCorrection; - public CostasPhaseErrorDetector( FrameSync frameSync, - LSMDemodulator demodulator, double correction ) + public CostasPhaseErrorDetector( FrameSync frameSync, LSMDemodulator demodulator, double correction ) { super( frameSync.getSync() ); diff --git a/src/module/decode/p25/P25_C4FMDecoder.java b/src/module/decode/p25/P25_C4FMDecoder.java index 94f2be56..2e7b1114 100644 --- a/src/module/decode/p25/P25_C4FMDecoder.java +++ b/src/module/decode/p25/P25_C4FMDecoder.java @@ -1,19 +1,20 @@ /******************************************************************************* - * 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 + * 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 module.decode.p25; @@ -41,8 +42,7 @@ import dsp.filter.FilterFactory; import dsp.filter.Window.WindowType; import dsp.filter.fir.real.RealFIRFilter_RB_RB; -public class P25_C4FMDecoder extends P25Decoder - implements IFrequencyChangeListener, IFrequencyChangeProvider,IFilteredRealBufferListener +public class P25_C4FMDecoder extends P25Decoder implements IFilteredRealBufferListener { private final static Logger mLog = LoggerFactory.getLogger( P25_C4FMDecoder.class ); diff --git a/src/module/decode/p25/P25_LSMDecoder.java b/src/module/decode/p25/P25_LSMDecoder.java index 011ff83d..94521323 100644 --- a/src/module/decode/p25/P25_LSMDecoder.java +++ b/src/module/decode/p25/P25_LSMDecoder.java @@ -42,6 +42,7 @@ import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB; import dsp.gain.ComplexFeedForwardGainControl; import dsp.psk.LSMDemodulator; import dsp.psk.QPSKPolarSlicer; +import source.tuner.frequency.FrequencyChangeEvent; public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener { @@ -56,8 +57,7 @@ public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener private List mAvailableTaps; private ComplexFIRFilter_CB_CB mBasebandFilter; - private ComplexBufferToStreamConverter mStreamConverter = - new ComplexBufferToStreamConverter(); + private ComplexBufferToStreamConverter mStreamConverter = new ComplexBufferToStreamConverter(); private ComplexFeedForwardGainControl mAGC = new ComplexFeedForwardGainControl( 32 ); private LSMDemodulator mLSMDemodulator = new LSMDemodulator(); @@ -107,8 +107,24 @@ public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener mMessageFramer.dispose(); mMessageFramer = null; } - - @Override + + @Override + public void setFrequencyChangeListener(Listener listener) + { + } + + @Override + public void removeFrequencyChangeListener() + { + } + + @Override + public Listener getFrequencyChangeListener() + { + return null; + } + + @Override public Listener getComplexBufferListener() { return mBasebandFilter; diff --git a/src/source/Source.java b/src/source/Source.java index 2de310b8..932c2749 100644 --- a/src/source/Source.java +++ b/src/source/Source.java @@ -20,11 +20,13 @@ package source; import module.Module; import sample.SampleType; import sample.real.IOverflowListener; +import source.tuner.frequency.IFrequencyChangeListener; +import source.tuner.frequency.IFrequencyChangeProvider; /** * Abstract class to define the minimum functionality of a sample data provider. */ -public abstract class Source extends Module +public abstract class Source extends Module implements IFrequencyChangeListener, IFrequencyChangeProvider { protected SampleType mSampleType; protected IOverflowListener mOverflowListener; diff --git a/src/source/mixer/RealMixerSource.java b/src/source/mixer/RealMixerSource.java index 7ea8dd7b..cfc96997 100644 --- a/src/source/mixer/RealMixerSource.java +++ b/src/source/mixer/RealMixerSource.java @@ -36,11 +36,11 @@ import sample.real.RealBuffer; import source.RealSource; import source.SourceException; import dsp.gain.AutomaticGainControl; +import source.tuner.frequency.FrequencyChangeEvent; 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; @@ -48,13 +48,11 @@ public class RealMixerSource extends RealSource private BufferReader mBufferReader = new BufferReader(); private TargetDataLine mTargetDataLine; private AudioFormat mAudioFormat; - private String mDisplayName; private int mBytesPerFrame = 0; private ISampleAdapter mSampleAdapter; private AutomaticGainControl mAGC = new AutomaticGainControl(); - CopyOnWriteArrayList> mSampleListeners = - new CopyOnWriteArrayList>(); + CopyOnWriteArrayList> mSampleListeners = new CopyOnWriteArrayList>(); /** * Real Mixer Source - constructs a reader on the mixer/sound card target @@ -80,8 +78,27 @@ public class RealMixerSource extends RealSource mAudioFormat = format; mSampleAdapter = sampleAdapter; } - - @Override + + @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() { } @@ -133,23 +150,15 @@ public class RealMixerSource extends RealSource * buffer, otherwise send him a copy of the buffer */ if( it.hasNext() ) { -// next.receive( mAGC.process( samples.copyOf() ) ); next.receive( samples.copyOf() ); } else { -// next.receive( mAGC.process( samples ) ); next.receive( samples ); } } } - @Override - public String toString() - { - return mDisplayName; - } - public int getSampleRate() { if( mTargetDataLine != null ) diff --git a/src/source/tuner/TunerChannelSource.java b/src/source/tuner/TunerChannelSource.java index fa0cef46..7556e7b5 100644 --- a/src/source/tuner/TunerChannelSource.java +++ b/src/source/tuner/TunerChannelSource.java @@ -34,7 +34,9 @@ import source.ComplexSource; import source.SourceException; import source.tuner.frequency.FrequencyChangeEvent; import source.tuner.frequency.FrequencyChangeEvent.Event; +import source.tuner.frequency.IFrequencyChangeListener; import source.tuner.frequency.IFrequencyChangeProcessor; +import source.tuner.frequency.IFrequencyChangeProvider; import java.util.ArrayList; import java.util.List; @@ -65,6 +67,7 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang private ComplexPrimeCICDecimate mDecimationFilter; private Listener mListener; private IFrequencyChangeProcessor mFrequencyChangeProcessor; + private DownstreamProcessor mDownstreamFrequencyEventProcessor = new DownstreamProcessor(); private ScheduledFuture mTaskHandle; private long mTunerFrequency = 0; @@ -75,7 +78,6 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang private AtomicBoolean mRunning = new AtomicBoolean(); private boolean mExpended = false; - private int mCount; /** * Provides a Digital Drop Channel (DDC) to decimate the IQ output from a @@ -109,7 +111,8 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang mMixer = new Oscillator(frequencyOffset, mTuner.getTunerController().getSampleRate()); /* Fire a sample rate change event to setup the decimation chain */ - frequencyChanged(new FrequencyChangeEvent(Event.NOTIFICATION_SAMPLE_RATE_CHANGE, mTuner.getTunerController().getSampleRate())); + frequencyChanged(new FrequencyChangeEvent(Event.NOTIFICATION_SAMPLE_RATE_CHANGE, + mTuner.getTunerController().getSampleRate())); } /** @@ -131,6 +134,10 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang if(mRunning.compareAndSet(false, true)) { + //Broadcast current frequency and sample rate settings so all downstream components are aware + mDownstreamFrequencyEventProcessor.broadcastCurrentFrequency(); + mDownstreamFrequencyEventProcessor.broadcastCurrentSampleRate(); + //Schedule the decimation task to run every 9 ms (111 iterations/second), an odd periodicity relative //to the inbound periodicity of 20 ms, to attempt to avoid thread queue contention mTaskHandle = executor.scheduleAtFixedRate(mDecimationProcessor, 0, 9, TimeUnit.MILLISECONDS); @@ -184,6 +191,20 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang } } + /** + * Changes the frequency correction value and broadcasts the change to the registered downstream listener. + * @param correction current frequency correction value. + */ + private void setFrequencyCorrection(int correction) + { + mChannelFrequencyCorrection = correction; + + updateMixerFrequencyOffset(); + + mDownstreamFrequencyEventProcessor.broadcast( + new FrequencyChangeEvent( Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE, mChannelFrequencyCorrection)); + } + public Tuner getTuner() { return mTuner; @@ -242,50 +263,49 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang case NOTIFICATION_FREQUENCY_CHANGE: mTunerFrequency = event.getValue().longValue(); updateMixerFrequencyOffset(); - break; - case REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE: - mChannelFrequencyCorrection = event.getValue().intValue(); - updateMixerFrequencyOffset(); - - if(mFrequencyChangeProcessor != null) - { - mFrequencyChangeProcessor.frequencyChanged(new FrequencyChangeEvent( - Event.NOTIFICATION_CHANNEL_FREQUENCY_CORRECTION_CHANGE, mChannelFrequencyCorrection)); - } + //Reset frequency correction so that downstream components can recalculate the value + setFrequencyCorrection(0); break; case NOTIFICATION_SAMPLE_RATE_CHANGE: int sampleRate = event.getValue().intValue(); - - if(mTunerSampleRate != sampleRate) - { - mMixer.setSampleRate(sampleRate); - - /* Get new decimation filter */ - mDecimationFilter = FilterFactory.getDecimationFilter(sampleRate, CHANNEL_RATE, 1, - CHANNEL_PASS_FREQUENCY, 60, WindowType.HAMMING); - - /* re-add the original output listener */ - mDecimationFilter.setListener(mListener); - - mTunerSampleRate = sampleRate; - } + setSampleRate(sampleRate); break; default: break; } } + /** + * Updates the sample rate to the requested value and notifies any downstream components of the change + * @param sampleRate to set + */ + private void setSampleRate(int sampleRate) + { + if(mTunerSampleRate != sampleRate) + { + mMixer.setSampleRate(sampleRate); + + /* Get new decimation filter */ + mDecimationFilter = FilterFactory.getDecimationFilter(sampleRate, CHANNEL_RATE, 1, + CHANNEL_PASS_FREQUENCY, 60, WindowType.HAMMING); + + /* re-add the original output listener */ + mDecimationFilter.setListener(mListener); + + mTunerSampleRate = sampleRate; + + mDownstreamFrequencyEventProcessor.broadcastCurrentSampleRate(); + } + } + /** * Calculates the local mixer frequency offset from the tuned frequency, * channel's requested frequency, and channel frequency correction. */ private void updateMixerFrequencyOffset() { - long offset = mTunerFrequency - - mTunerChannel.getFrequency() - - mChannelFrequencyCorrection; - + long offset = mTunerFrequency - mTunerChannel.getFrequency() - mChannelFrequencyCorrection; mMixer.setFrequency(offset); } @@ -299,6 +319,140 @@ public class TunerChannelSource extends ComplexSource implements IFrequencyChang return mTunerChannel.getFrequency(); } + /** + * Implements IFrequencyChangeProvider to enable this source to broadcast frequency change events to downstream + * listeners. + * + * @param listener to receive downstream events + */ + @Override + public void setFrequencyChangeListener(Listener listener) + { + mDownstreamFrequencyEventProcessor.setFrequencyChangeListener(listener); + } + + /** + * Implements IFrequencyChangeProvider to remove the frequency change listener from receiving down-stream frequency + * change events. + */ + @Override + public void removeFrequencyChangeListener() + { + mDownstreamFrequencyEventProcessor.removeFrequencyChangeListener(); + } + + /** + * Implements IFrequencyChangeListener to receive frequency change events containing requests from downstream + * listeners to change frequency values. + * @return listener + */ + @Override + public Listener getFrequencyChangeListener() + { + return mDownstreamFrequencyEventProcessor.getFrequencyChangeListener(); + } + + /** + * 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 + * notified of any frequency or sample rate change events and will also be able to request frequency correction + * updates. + */ + public class DownstreamProcessor implements IFrequencyChangeListener, IFrequencyChangeProvider, + Listener + { + //Listener to receive downstream events + private Listener mListener; + + /** + * Broadcasts the frequency change event to the downstream frequency change listener + * @param event to broadcast + */ + public void broadcast(FrequencyChangeEvent event) + { + if(mListener != null) + { + mListener.receive(event); + } + } + + /** + * Broadcasts the current frequency of this tuner channel source to the downstream listener + */ + public void broadcastCurrentFrequency() + { + try + { + long frequency = getFrequency(); + broadcast(new FrequencyChangeEvent(Event.NOTIFICATION_FREQUENCY_CHANGE, frequency)); + } + catch(SourceException se) + { + mLog.error("Error obtaining frequency from tuner to broadcast downstream"); + } + } + + /** + * Broadcasts the current decimated sample rate of this tuner channel source + */ + public void broadcastCurrentSampleRate() + { + try + { + //Note: downstream sample rate is currently a fixed value -- it will change in the future + broadcast(new FrequencyChangeEvent(Event.NOTIFICATION_SAMPLE_RATE_CHANGE, getSampleRate())); + } + catch(SourceException se) + { + mLog.error("Error obtaining sample rate from tuner to broadcast downstream"); + } + } + + /** + * Sets the downstream listener to receive frequency change events from this tuner channel source + * @param listener to receive events + */ + @Override + public void setFrequencyChangeListener(Listener listener) + { + mListener = listener; + } + + /** + * Removes the downstream listener from receiving frequency change events. + */ + @Override + public void removeFrequencyChangeListener() + { + mListener = null; + } + + /** + * Listener for receiving frequency change events from downstream components + */ + @Override + public Listener getFrequencyChangeListener() + { + return this; + } + + /** + * Processes frequency change events from downstream components. + * @param event to process + */ + @Override + public void receive(FrequencyChangeEvent event) + { + switch(event.getEvent()) + { + //Frequency correction requests are the only change requests supported from downstream components + case REQUEST_CHANNEL_FREQUENCY_CORRECTION_CHANGE: + setFrequencyCorrection(event.getValue().intValue()); + break; + } + } + } + /** * Decimates an inbound buffer of I/Q samples from the source down to the * standard 48000 channel sample rate diff --git a/src/source/tuner/frequency/IFrequencyChangeProvider.java b/src/source/tuner/frequency/IFrequencyChangeProvider.java index 919126e7..354e9af9 100644 --- a/src/source/tuner/frequency/IFrequencyChangeProvider.java +++ b/src/source/tuner/frequency/IFrequencyChangeProvider.java @@ -1,9 +1,30 @@ +/******************************************************************************* + * 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 source.tuner.frequency; import sample.Listener; +/** + * Provider of Frequency Change Events + */ public interface IFrequencyChangeProvider { - public void setFrequencyChangeListener( Listener listener ); - public void removeFrequencyChangeListener(); + void setFrequencyChangeListener( Listener listener ); + void removeFrequencyChangeListener(); } diff --git a/src/source/wave/ComplexWaveSource.java b/src/source/wave/ComplexWaveSource.java index efa8b14b..8d80ce1d 100644 --- a/src/source/wave/ComplexWaveSource.java +++ b/src/source/wave/ComplexWaveSource.java @@ -1,19 +1,20 @@ /******************************************************************************* - * 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 + * 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 source.wave; @@ -36,6 +37,7 @@ import sample.complex.ComplexBuffer; import source.ComplexSource; import source.IControllableFileSource; import source.IFrameLocationListener; +import source.tuner.frequency.FrequencyChangeEvent; public class ComplexWaveSource extends ComplexSource implements IControllableFileSource { @@ -54,8 +56,27 @@ public class ComplexWaveSource extends ComplexSource implements IControllableFil { mFile = file; } - - @Override + + @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() { stop(); diff --git a/src/source/wave/RealWaveSource.java b/src/source/wave/RealWaveSource.java index 06e4fc25..4b9b120b 100644 --- a/src/source/wave/RealWaveSource.java +++ b/src/source/wave/RealWaveSource.java @@ -36,6 +36,7 @@ import sample.real.RealBuffer; import source.IControllableFileSource; import source.IFrameLocationListener; import source.RealSource; +import source.tuner.frequency.FrequencyChangeEvent; public class RealWaveSource extends RealSource implements IControllableFileSource, AutoCloseable @@ -55,8 +56,27 @@ public class RealWaveSource extends RealSource { mFile = file; } - - + + @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() { diff --git a/src/spectrum/OverlayPanel.java b/src/spectrum/OverlayPanel.java index ae0c23fc..b064b804 100644 --- a/src/spectrum/OverlayPanel.java +++ b/src/spectrum/OverlayPanel.java @@ -50,6 +50,7 @@ public class OverlayPanel extends JPanel implements ChannelEventListener, IFrequ private static final long serialVersionUID = 1L; private final static Logger mLog = LoggerFactory.getLogger(OverlayPanel.class); + private final DecimalFormat PPM_FORMATTER = new DecimalFormat( "#.0" ); private final static RenderingHints RENDERING_HINTS = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); @@ -187,7 +188,6 @@ public class OverlayPanel extends JPanel implements ChannelEventListener, IFrequ * 6 64x Zoom * * @param zoom level, 0 - 6. - * @param offset into the FFT bins for start zoom window */ public void setZoom(int zoom) { @@ -412,20 +412,41 @@ public class OverlayPanel extends JPanel implements ChannelEventListener, IFrequ } /** - * Draws a vertical line at the xaxis + * Draws the Automatic Frequency Control (AFC) channel center offset */ - private void drawAFC(Graphics2D graphics, double frequencyAxis, double errorAxis) + private void drawAFC(Graphics2D graphics, double frequencyAxis, double errorAxis, double bandwidth, + int correction, long frequency) { double height = getSize().getHeight() - mSpectrumInset; - double lineHeight = height * 0.75d; + double verticalAxisTop = height * 0.88d; + double verticalAxisBottom = height * 0.98d; + + double halfBandwidth = bandwidth / 2.0; + double errorEdgeStart = errorAxis - halfBandwidth; + double errorEdgeStop = errorAxis + halfBandwidth; graphics.setColor(Color.YELLOW); - //Vertical frequency error line - graphics.draw(new Line2D.Double(errorAxis, lineHeight, errorAxis, height - 1.0d)); + //Horizontal line connecting frequency and error line + graphics.draw(new Line2D.Double(errorEdgeStart, verticalAxisBottom, errorEdgeStop, verticalAxisBottom)); - //Horizontal line connecting frequency and error lines - graphics.draw(new Line2D.Double(frequencyAxis, lineHeight, errorAxis, lineHeight)); + //Vertical band edge lines + graphics.draw(new Line2D.Double(errorEdgeStart, verticalAxisTop, errorEdgeStart, verticalAxisBottom)); + graphics.draw(new Line2D.Double(errorEdgeStop, verticalAxisTop, errorEdgeStop, verticalAxisBottom)); + + double ppm = (double)correction / ((double)frequency / 1E6d); + + String label = "PPM " + PPM_FORMATTER.format(ppm) ; + + FontMetrics fontMetrics = graphics.getFontMetrics(this.getFont()); + + Rectangle2D rect = fontMetrics.getStringBounds(label, graphics); + + //Only render the correction value label if the spacing is large enough + if(rect.getWidth() <= bandwidth && rect.getHeight() * 5 <= height) + { + graphics.drawString(label, (float)(errorEdgeStart + 1.0), (float)(verticalAxisBottom - 2.0)); + } } /** @@ -564,7 +585,8 @@ public class OverlayPanel extends JPanel implements ChannelEventListener, IFrequ { long error = frequency + correction; - drawAFC(graphics, frequencyAxis, getAxisFromFrequency(error)); + drawAFC(graphics, frequencyAxis, getAxisFromFrequency(error), width, correction, + tunerChannel.getFrequency()); } } } diff --git a/src/spectrum/SpectrumPanel.java b/src/spectrum/SpectrumPanel.java index d16b35a8..156ab50f 100644 --- a/src/spectrum/SpectrumPanel.java +++ b/src/spectrum/SpectrumPanel.java @@ -1,493 +1,483 @@ /******************************************************************************* - * 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 + * 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 spectrum; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.GradientPaint; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Rectangle; -import java.awt.RenderingHints; -import java.awt.geom.GeneralPath; -import java.awt.geom.Line2D; -import java.util.Arrays; - -import javax.swing.JPanel; - -import org.apache.commons.lang3.Validate; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import settings.ColorSetting; -import settings.ColorSetting.ColorSettingName; -import settings.Setting; -import settings.SettingChangeListener; -import settings.SettingsManager; import dsp.filter.smoothing.GaussianSmoothingFilter; import dsp.filter.smoothing.NoSmoothingFilter; import dsp.filter.smoothing.RectangularSmoothingFilter; import dsp.filter.smoothing.SmoothingFilter; import dsp.filter.smoothing.SmoothingFilter.SmoothingType; import dsp.filter.smoothing.TriangularSmoothingFilter; +import org.apache.commons.lang3.Validate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import settings.ColorSetting; +import settings.ColorSetting.ColorSettingName; +import settings.Setting; +import settings.SettingChangeListener; +import settings.SettingsManager; -public class SpectrumPanel extends JPanel - implements DFTResultsListener, - SettingChangeListener, - SpectralDisplayAdjuster +import javax.swing.*; +import java.awt.*; +import java.awt.geom.GeneralPath; +import java.awt.geom.Line2D; +import java.util.Arrays; + +public class SpectrumPanel extends JPanel implements DFTResultsListener, SettingChangeListener, SpectralDisplayAdjuster { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; - private final static Logger mLog = - LoggerFactory.getLogger( SpectrumPanel.class ); - - private static final RenderingHints RENDERING_HINTS = - new RenderingHints( RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON ); + private final static Logger mLog = LoggerFactory.getLogger(SpectrumPanel.class); - static - { - RENDERING_HINTS.put( RenderingHints.KEY_RENDERING, - RenderingHints.VALUE_RENDER_QUALITY ); - } - - private Color mColorSpectrumBackground; - private Color mColorSpectrumGradientTop; - private Color mColorSpectrumGradientBottom; - private Color mColorSpectrumLine; + private static final RenderingHints RENDERING_HINTS = + new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - //Defines the panel inset along the bottom for frequency display - private float mSpectrumInset = 20.0f; - - //Current DFT output bins in dB - private float[] mDisplayFFTBins = new float[ 1 ]; - - //Averaging across multiple DFT result sets - private int mAveraging = 4; - - //Smoothing across bins in the same DFT result set - private SmoothingFilter mSmoothingFilter = new GaussianSmoothingFilter(); - - //Reference dB value set according to the source sample size - private float mDBScale; - - private int mZoom = 0; - private int mZoomWindowOffset = 0;; - - private SettingsManager mSettingsManager; - - public SpectrumPanel( SettingsManager settingsManager ) + static { - mSettingsManager = settingsManager; - - if( mSettingsManager != null ) - { - mSettingsManager.addListener( this ); - } - - setSampleSize( 16.0 ); - - mSmoothingFilter.setPointSize( SmoothingFilter.SMOOTHING_DEFAULT ); - - getColors(); - - setAveraging( mAveraging ); + RENDERING_HINTS.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); } - - public void dispose() - { - if( mSettingsManager != null ) - { - mSettingsManager.removeListener( this ); - } - - mSettingsManager = null; - } - + + private Color mColorSpectrumBackground; + private Color mColorSpectrumGradientTop; + private Color mColorSpectrumGradientBottom; + private Color mColorSpectrumLine; + + //Defines the panel inset along the bottom for frequency display + private float mSpectrumInset = 20.0f; + + //Current DFT output bins in dB + private float[] mDisplayFFTBins = new float[1]; + + //Averaging across multiple DFT result sets + private int mAveraging = 4; + + //Smoothing across bins in the same DFT result set + private SmoothingFilter mSmoothingFilter = new GaussianSmoothingFilter(); + + //Reference dB value set according to the source sample size + private float mDBScale; + + private int mZoom = 0; + private int mZoomWindowOffset = 0; + ; + + private SettingsManager mSettingsManager; + + public SpectrumPanel(SettingsManager settingsManager) + { + mSettingsManager = settingsManager; + + if(mSettingsManager != null) + { + mSettingsManager.addListener(this); + } + + setSampleSize(16.0); + + mSmoothingFilter.setPointSize(SmoothingFilter.SMOOTHING_DEFAULT); + + getColors(); + + setAveraging(mAveraging); + } + + public void dispose() + { + if(mSettingsManager != null) + { + mSettingsManager.removeListener(this); + } + + mSettingsManager = null; + } + /** * DFTResultsListener interface for receiving the processed data * to display */ - public void receive( float[] currentFFTBins ) + public void receive(float[] currentFFTBins) { - //Prevent arrays of NaN values from being rendered. The first few - //DFT result sets on startup will contain NaN values - if( Float.isInfinite( currentFFTBins[ 0 ] ) || Float.isNaN( currentFFTBins[ 0 ] ) ) - { - currentFFTBins = new float[ currentFFTBins.length ]; - } + //Prevent arrays of NaN values from being rendered. The first few + //DFT result sets on startup will contain NaN values + if(Float.isInfinite(currentFFTBins[0]) || Float.isNaN(currentFFTBins[0])) + { + currentFFTBins = new float[currentFFTBins.length]; + } - //Construct and/or resize our DFT results variables - if( mDisplayFFTBins == null || - mDisplayFFTBins.length != currentFFTBins.length ) - { - mDisplayFFTBins = currentFFTBins; - } + //Construct and/or resize our DFT results variables + if(mDisplayFFTBins == null || + mDisplayFFTBins.length != currentFFTBins.length) + { + mDisplayFFTBins = currentFFTBins; + } - //Apply smoothing across the bins of the DFT results - float[] smoothedBins = mSmoothingFilter.filter( currentFFTBins ); + //Apply smoothing across the bins of the DFT results + float[] smoothedBins = mSmoothingFilter.filter(currentFFTBins); - //Apply averaging over multiple DFT output frames - if( mAveraging > 1 ) - { - float gain = 1.0f / (float)mAveraging; - - for( int x = 0; x < mDisplayFFTBins.length; x++ ) - { - mDisplayFFTBins[ x ] += - ( smoothedBins[ x ] - mDisplayFFTBins[ x ] ) * gain; - } - } - else - { - mDisplayFFTBins = smoothedBins; - } - - repaint(); + //Apply averaging over multiple DFT output frames + if(mAveraging > 1) + { + float gain = 1.0f / (float)mAveraging; + + for(int x = 0; x < mDisplayFFTBins.length; x++) + { + mDisplayFFTBins[x] += + (smoothedBins[x] - mDisplayFFTBins[x]) * gain; + } + } + else + { + mDisplayFFTBins = smoothedBins; + } + + repaint(); } @Override - public void paintComponent( Graphics g ) + public void paintComponent(Graphics g) { - super.paintComponent( g ); - - Graphics2D graphics = (Graphics2D) g; + super.paintComponent(g); - graphics.setBackground( mColorSpectrumBackground ); + Graphics2D graphics = (Graphics2D)g; - graphics.setRenderingHints( RENDERING_HINTS ); - - drawSpectrum( graphics ); + graphics.setBackground(mColorSpectrumBackground); + + graphics.setRenderingHints(RENDERING_HINTS); + + drawSpectrum(graphics); } - + /** * Draws the current fft spectrum with a line and a gradient fill. */ - private void drawSpectrum( Graphics2D graphics ) + private void drawSpectrum(Graphics2D graphics) { - Dimension size = getSize(); + Dimension size = getSize(); - //Draw the background - Rectangle background = new Rectangle( 0, 0, size.width, size.height ); - graphics.setColor( mColorSpectrumBackground ); - graphics.draw( background ); - graphics.fill( background ); + //Draw the background + Rectangle background = new Rectangle(0, 0, size.width, size.height); + graphics.setColor(mColorSpectrumBackground); + graphics.draw(background); + graphics.fill(background); - //Define the gradient - GradientPaint gradient = new GradientPaint( 0, - ( getSize().height - mSpectrumInset ) / 2, - mColorSpectrumGradientTop, - 0, - getSize().height, - mColorSpectrumGradientBottom ); - - graphics.setBackground( mColorSpectrumBackground ); - - GeneralPath spectrumShape = new GeneralPath(); + //Define the gradient + GradientPaint gradient = new GradientPaint(0, + (getSize().height - mSpectrumInset) / 2, + mColorSpectrumGradientTop, + 0, + getSize().height, + mColorSpectrumGradientBottom); - //Start at the lower right inset point - spectrumShape.moveTo( size.getWidth(), - size.getHeight() - mSpectrumInset ); - - //Draw to the lower left - spectrumShape.lineTo( 0, size.getHeight() - mSpectrumInset ); + graphics.setBackground(mColorSpectrumBackground); - float[] bins = getBins(); - - //If we have FFT data to display ... - if( bins != null ) - { - float insideHeight = size.height - mSpectrumInset; + GeneralPath spectrumShape = new GeneralPath(); - float scalor = insideHeight / -mDBScale; + //Start at the lower right inset point + spectrumShape.moveTo(size.getWidth(), + size.getHeight() - mSpectrumInset); + + //Draw to the lower left + spectrumShape.lineTo(0, size.getHeight() - mSpectrumInset); + + float[] bins = getBins(); + + //If we have FFT data to display ... + if(bins != null) + { + float insideHeight = size.height - mSpectrumInset; + + float scalor = insideHeight / -mDBScale; /* Calculate based on bin size - 1, since bin 0 is rendered at zero - * and the last bin is rendered at the width */ - float binSize = (float)size.width / ( (float)( bins.length ) ); + * and the last bin is rendered at the width */ + float binSize = (float)size.width / ((float)(bins.length)); - for( int x = 0; x < bins.length; x++ ) - { - float height; - - height = bins[ x ] * scalor; - - if( height > insideHeight ) - { - height = insideHeight; - } - - if( height < 0 ) - { - height = 0; - } - - float xAxis = (float)x * binSize; + for(int x = 0; x < bins.length; x++) + { + float height; - spectrumShape.lineTo( xAxis, height ); - } - } - //Otherwise show an empty spectrum - else - { - //Draw Left Size - graphics.setPaint( gradient ); - spectrumShape.lineTo( 0, size.getHeight() - mSpectrumInset ); - //Draw Middle - spectrumShape.lineTo( size.getWidth(), - size.getHeight() - mSpectrumInset ); - } - - //Draw Right Side - spectrumShape.lineTo( size.getWidth(), - size.getHeight() - mSpectrumInset ); - - graphics.setPaint( gradient ); - graphics.draw( spectrumShape ); - graphics.fill( spectrumShape ); - - graphics.setPaint( mColorSpectrumLine ); + height = bins[x] * scalor; - //Draw the bottom line under the spectrum - graphics.draw( new Line2D.Float( 0, - size.height - mSpectrumInset, - size.width, - size.height - mSpectrumInset ) ); + if(height > insideHeight) + { + height = insideHeight; + } + + if(height < 0) + { + height = 0; + } + + float xAxis = (float)x * binSize; + + spectrumShape.lineTo(xAxis, height); + } + } + //Otherwise show an empty spectrum + else + { + //Draw Left Size + graphics.setPaint(gradient); + spectrumShape.lineTo(0, size.getHeight() - mSpectrumInset); + //Draw Middle + spectrumShape.lineTo(size.getWidth(), + size.getHeight() - mSpectrumInset); + } + + //Draw Right Side + spectrumShape.lineTo(size.getWidth(), + size.getHeight() - mSpectrumInset); + + graphics.setPaint(gradient); + graphics.draw(spectrumShape); + graphics.fill(spectrumShape); + + graphics.setPaint(mColorSpectrumLine); + + //Draw the bottom line under the spectrum + graphics.draw(new Line2D.Float(0, + size.height - mSpectrumInset, + size.width, + size.height - mSpectrumInset)); } /** * Sets the current zoom level - * + * * 0 No Zoom * 1 2x Zoom * 2 4x Zoom * 3 8x Zoom * 4 16x Zoom * 5 32x Zoom - * - * @param zoom level, 0 - 5. + * 6 64x Zoom + * + * @param zoom level, 0 - 6. */ - public void setZoom( int zoom ) + public void setZoom(int zoom) { - Validate.isTrue( 0 <= zoom && zoom <= 5 ); - - mZoom = zoom; + Validate.isTrue(0 <= zoom && zoom <= 6, "Unrecognized Zoom Level: " + zoom); + + mZoom = zoom; } /** * Sets the offset to define which subset of zoomed bins to display - * + * * @param offset */ - public void setZoomWindowOffset( int offset ) + public void setZoomWindowOffset(int offset) { - mZoomWindowOffset = offset; + mZoomWindowOffset = offset; } - /** - * Sets the source sample size in bits which defines the lowest dB value to - * display in the spectrum - * - * @param sampleSize in bits - */ - public void setSampleSize( double sampleSize ) - { - Validate.isTrue(2.0 <= sampleSize && sampleSize <= 32.0); - - mDBScale = (float)( 20.0 * Math.log10( Math.pow( 2.0, sampleSize - 1 ) ) ); - } - - /** - * Defines the number of DFT result sets to average across - */ - public void setAveraging( int size ) - { - mAveraging = size; - } - - /** - * DFT result sets averaging value - */ - public int getAveraging() - { - return mAveraging; - } - - /** - * Clears the spectral display - */ - public void clearSpectrum() - { - mDisplayFFTBins = null; - - repaint(); - } - - private void getColors() - { - mColorSpectrumBackground = - getColor( ColorSettingName.SPECTRUM_BACKGROUND ); - - mColorSpectrumGradientBottom = - getColor( ColorSettingName.SPECTRUM_GRADIENT_BOTTOM ); - - mColorSpectrumGradientTop = - getColor( ColorSettingName.SPECTRUM_GRADIENT_TOP ); - - mColorSpectrumLine = getColor( ColorSettingName.SPECTRUM_LINE ); - } - - /** - * Retrieves the current setting for the named color setting - */ - private Color getColor( ColorSettingName name ) - { - ColorSetting setting = mSettingsManager.getColorSetting( name ); - - return setting.getColor(); - } - - /** - * Listener interface to receive setting change notifications - */ - @Override - public void settingChanged( Setting setting ) + /** + * Sets the source sample size in bits which defines the lowest dB value to + * display in the spectrum + * + * @param sampleSize in bits + */ + public void setSampleSize(double sampleSize) { - if( setting instanceof ColorSetting ) - { - ColorSetting colorSetting = (ColorSetting)setting; - - switch( ( (ColorSetting) setting ).getColorSettingName() ) - { - case SPECTRUM_BACKGROUND: - mColorSpectrumBackground = colorSetting.getColor(); - break; - case SPECTRUM_GRADIENT_BOTTOM: - mColorSpectrumGradientBottom = colorSetting.getColor(); - break; - case SPECTRUM_GRADIENT_TOP: - mColorSpectrumGradientTop = colorSetting.getColor(); - break; - case SPECTRUM_LINE: - mColorSpectrumLine = colorSetting.getColor(); - break; - default: - break; - } - } + Validate.isTrue(2.0 <= sampleSize && sampleSize <= 32.0); + + mDBScale = (float)(20.0 * Math.log10(Math.pow(2.0, sampleSize - 1))); } - - private int getZoomMultiplier() - { - return (int)Math.pow( 2.0, mZoom ); - } - - /** - * Returns the DFT result bins, or a zoomed and offset version of the bins - * when the display is zoomed. - */ + + /** + * Defines the number of DFT result sets to average across + */ + public void setAveraging(int size) + { + mAveraging = size; + } + + /** + * DFT result sets averaging value + */ + public int getAveraging() + { + return mAveraging; + } + + /** + * Clears the spectral display + */ + public void clearSpectrum() + { + mDisplayFFTBins = null; + + repaint(); + } + + private void getColors() + { + mColorSpectrumBackground = + getColor(ColorSettingName.SPECTRUM_BACKGROUND); + + mColorSpectrumGradientBottom = + getColor(ColorSettingName.SPECTRUM_GRADIENT_BOTTOM); + + mColorSpectrumGradientTop = + getColor(ColorSettingName.SPECTRUM_GRADIENT_TOP); + + mColorSpectrumLine = getColor(ColorSettingName.SPECTRUM_LINE); + } + + /** + * Retrieves the current setting for the named color setting + */ + private Color getColor(ColorSettingName name) + { + ColorSetting setting = mSettingsManager.getColorSetting(name); + + return setting.getColor(); + } + + /** + * Listener interface to receive setting change notifications + */ + @Override + public void settingChanged(Setting setting) + { + if(setting instanceof ColorSetting) + { + ColorSetting colorSetting = (ColorSetting)setting; + + switch(((ColorSetting)setting).getColorSettingName()) + { + case SPECTRUM_BACKGROUND: + mColorSpectrumBackground = colorSetting.getColor(); + break; + case SPECTRUM_GRADIENT_BOTTOM: + mColorSpectrumGradientBottom = colorSetting.getColor(); + break; + case SPECTRUM_GRADIENT_TOP: + mColorSpectrumGradientTop = colorSetting.getColor(); + break; + case SPECTRUM_LINE: + mColorSpectrumLine = colorSetting.getColor(); + break; + default: + break; + } + } + } + + private int getZoomMultiplier() + { + return (int)Math.pow(2.0, mZoom); + } + + /** + * Returns the DFT result bins, or a zoomed and offset version of the bins + * when the display is zoomed. + */ private float[] getBins() { - if( mZoom == 0 ) - { - return mDisplayFFTBins; - } - else - { - int length = mDisplayFFTBins.length / getZoomMultiplier(); - - int offset = mZoomWindowOffset; - - if( ( offset + length ) >= mDisplayFFTBins.length ) - { - offset = mDisplayFFTBins.length - length; - } - - if( offset < 0 ) - { - offset = 0; - } + if(mZoom == 0) + { + return mDisplayFFTBins; + } + else + { + int length = mDisplayFFTBins.length / getZoomMultiplier(); - return Arrays.copyOfRange( mDisplayFFTBins, offset, offset + length ); - } + int offset = mZoomWindowOffset; + + if((offset + length) >= mDisplayFFTBins.length) + { + offset = mDisplayFFTBins.length - length; + } + + if(offset < 0) + { + offset = 0; + } + + return Arrays.copyOfRange(mDisplayFFTBins, offset, offset + length); + } } - @Override - public void settingDeleted( Setting setting ) { /* not implemented */ } + @Override + public void settingDeleted(Setting setting) + { /* not implemented */ } - /** - * Returns the current inter-bin smoothing setting - */ - @Override - public int getSmoothing() - { - return mSmoothingFilter.getPointSize(); - } + /** + * Returns the current inter-bin smoothing setting + */ + @Override + public int getSmoothing() + { + return mSmoothingFilter.getPointSize(); + } - /** - * Sets the bin smoothing value to define how wide the smoothing window is - * when averaging across DFT bins - */ - @Override - public void setSmoothing( int smoothing ) - { - mSmoothingFilter.setPointSize( smoothing ); - } + /** + * Sets the bin smoothing value to define how wide the smoothing window is + * when averaging across DFT bins + */ + @Override + public void setSmoothing(int smoothing) + { + mSmoothingFilter.setPointSize(smoothing); + } - /** - * Returns the current smoothing filter type - */ - @Override - public SmoothingType getSmoothingType() - { - return mSmoothingFilter.getSmoothingType(); - } + /** + * Returns the current smoothing filter type + */ + @Override + public SmoothingType getSmoothingType() + { + return mSmoothingFilter.getSmoothingType(); + } - /** - * Sets the smoothing filter type - */ - @Override - public void setSmoothingType( SmoothingType type ) - { - if( mSmoothingFilter.getSmoothingType() != type ) - { - int pointSize = getSmoothing(); + /** + * Sets the smoothing filter type + */ + @Override + public void setSmoothingType(SmoothingType type) + { + if(mSmoothingFilter.getSmoothingType() != type) + { + int pointSize = getSmoothing(); - synchronized ( mSmoothingFilter ) - { - switch( type ) - { - case GAUSSIAN: - mSmoothingFilter = new GaussianSmoothingFilter(); - break; - case RECTANGLE: - mSmoothingFilter = new RectangularSmoothingFilter(); - break; - case TRIANGLE: - mSmoothingFilter = new TriangularSmoothingFilter(); - break; - case NONE: - default: - mSmoothingFilter = new NoSmoothingFilter(); - break; - } - } - - setSmoothing( pointSize ); - } - } + synchronized(mSmoothingFilter) + { + switch(type) + { + case GAUSSIAN: + mSmoothingFilter = new GaussianSmoothingFilter(); + break; + case RECTANGLE: + mSmoothingFilter = new RectangularSmoothingFilter(); + break; + case TRIANGLE: + mSmoothingFilter = new TriangularSmoothingFilter(); + break; + case NONE: + default: + mSmoothingFilter = new NoSmoothingFilter(); + break; + } + } + + setSmoothing(pointSize); + } + } } From ae8a44caed0de0de9a995714e30f678f3c3ca65b Mon Sep 17 00:00:00 2001 From: Denny Date: Sat, 11 Mar 2017 11:53:12 -0500 Subject: [PATCH 10/20] Removes cic cleanup filter from processing chain. --- .../filter/cic/ComplexPrimeCICDecimate.java | 754 +++++++++--------- 1 file changed, 379 insertions(+), 375 deletions(-) diff --git a/src/dsp/filter/cic/ComplexPrimeCICDecimate.java b/src/dsp/filter/cic/ComplexPrimeCICDecimate.java index 72a869d0..95bf15ce 100644 --- a/src/dsp/filter/cic/ComplexPrimeCICDecimate.java +++ b/src/dsp/filter/cic/ComplexPrimeCICDecimate.java @@ -1,408 +1,412 @@ /******************************************************************************* - * SDR Trunk - * Copyright (C) 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 + * 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 dsp.filter.cic; -import java.util.ArrayList; -import java.util.List; - +import dsp.filter.FilterFactory; +import dsp.filter.Filters; +import dsp.filter.Window.WindowType; +import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB; +import dsp.filter.halfband.complex.HalfBandFilter_CB_CB; import org.apache.commons.lang3.Validate; import sample.Listener; import sample.complex.ComplexBuffer; import sample.complex.ComplexSampleListener; import sample.complex.ComplexToComplexBufferAssembler; import sample.decimator.ComplexDecimator; -import dsp.filter.FilterFactory; -import dsp.filter.Filters; -import dsp.filter.Window.WindowType; -import dsp.filter.fir.complex.ComplexFIRFilter_CB_CB; -import dsp.filter.halfband.complex.HalfBandFilter_CB_CB; + +import java.util.ArrayList; +import java.util.List; public class ComplexPrimeCICDecimate implements Listener { - public static int[] PRIMES = { 2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53, - 59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151, - 157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251, - 257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359, - 367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463, - 467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593, - 599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691 }; - - private ArrayList mDecimatingStages = - new ArrayList(); - - private DecimatingStage mFirstDecimatingStage; + public static int[] PRIMES = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, + 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, + 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, + 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, + 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, + 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, + 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691}; - private Output mOutput; + private ArrayList mDecimatingStages = + new ArrayList(); - /** - * Non-Recursive Prime-Factor CIC Filter with float sample array inputs and - * decimated, single paired i/q float sample output. - * - * Implements the CIC filter described in Understanding Digital Signal - * Processing, 3e, Lyons, on page 769. This filter is comprised of multiple - * decimating stages each with a prime factor decimation rate. Multiple - * stages are cascaded to achieve the overall decimation rate. - * - * This filter supports a maximum decimation rate of 700. This filter can - * be adapted to higher decimation rates by adding additional prime factors - * to the PRIMES array. - * - * @param decimation - overall decimation rate - * @param order - filter order - */ - public ComplexPrimeCICDecimate( int decimation, int order, - int passFrequency, int attenuation, WindowType windowType ) - { - Validate.isTrue(decimation <= 700); - + private DecimatingStage mFirstDecimatingStage; - List stageSizes = getPrimeFactors( decimation ); - - for( int x = 0; x < stageSizes.size(); x++ ) - { - DecimatingStage stage = new DecimatingStage( stageSizes.get( x ), order ); - - mDecimatingStages.add( stage ); - - if( x == 0 ) - { - /* Reference to first stage -- will receive all samples */ - mFirstDecimatingStage = stage; - } - else - { - /* Wire the current stage to the previous stage */ - mDecimatingStages.get( x - 1 ).setListener( stage ); - } - } - - mOutput = new Output( 48000, passFrequency, attenuation, windowType ); - - mDecimatingStages.get( mDecimatingStages.size() - 1 ) - .setListener( mOutput ); - } - - public void dispose() - { - for( DecimatingStage stage: mDecimatingStages ) - { - stage.dispose(); - } - - mDecimatingStages.clear(); - mDecimatingStages = null; - mFirstDecimatingStage = null; - - mOutput.dispose(); - mOutput = null; - } - - /** - * Adds a listener to receive the output of this CIC decimation filter - */ - public void setListener( Listener listener ) - { - mOutput.setListener( listener ); - } - - /** - * Removes listener from output of this CIC decimation filter - */ - public void removeListener() - { - mOutput.removeListener(); - } + private Output mOutput; - /** - * Calculates the prime factors of the decimation rate up to a maximum - * decimation rate of 700. If you wish to have decimation rates higher - * than 700, then add additional prime factors to the PRIMES array. - * - * @param decimation - integral decimation rate - * @return - ordered list (smallest to largest) of prime factors - */ - public static List getPrimeFactors( int decimation ) - { - ArrayList stages = new ArrayList(); - - int pointer = 0; - - while( decimation > 0 && pointer < PRIMES.length ) - { - int prime = PRIMES[ pointer ]; - - if( decimation % prime == 0 ) - { - stages.add( prime ); - - decimation /= prime; - } - else - { - pointer++; - } - } - - return stages; - } - - /** - * Primary input method for receiving sample arrays composed as I,Q,I,Q, etc. - */ - @Override - public void receive( ComplexBuffer buffer ) + /** + * Non-Recursive Prime-Factor CIC Filter with float sample array inputs and + * decimated, single paired i/q float sample output. + * + * Implements the CIC filter described in Understanding Digital Signal + * Processing, 3e, Lyons, on page 769. This filter is comprised of multiple + * decimating stages each with a prime factor decimation rate. Multiple + * stages are cascaded to achieve the overall decimation rate. + * + * This filter supports a maximum decimation rate of 700. This filter can + * be adapted to higher decimation rates by adding additional prime factors + * to the PRIMES array. + * + * @param decimation - overall decimation rate + * @param order - filter order + */ + public ComplexPrimeCICDecimate(int decimation, int order, + int passFrequency, int attenuation, WindowType windowType) { - if( mFirstDecimatingStage != null ) - { - float[] samples = buffer.getSamples(); - - for( int x = 0; x < samples.length; x += 2 ) - { - mFirstDecimatingStage.receive( samples[ x ], samples[ x + 1 ] ); - } - } - } - - /** - * Decimating stage combines multiple CIC stages with a decimator. The - * number of stages is indicated by the order value and the size indicates - * the decimation rate of this stage. - */ - public class DecimatingStage implements ComplexSampleListener - { - private ArrayList mStages = new ArrayList(); - private Stage mFirstStage; - private ComplexDecimator mDecimator; - - public DecimatingStage( int size, int order ) - { - for( int x = 0; x < order; x++ ) - { - Stage stage; - - if( size == 2 ) - { - stage = new TwoStage(); - } - else - { - stage = new Stage( size ); - } - - mStages.add( stage ); - - if( x == 0 ) - { - mFirstStage = stage; - } - else - { - mStages.get( x - 1 ).setListener( stage ); - } - } - - mDecimator = new ComplexDecimator( size ); - - mStages.get( mStages.size() - 1 ).setListener( mDecimator ); - } - - public void dispose() - { - for( Stage stage: mStages ) - { - stage.dispose(); - } - - mStages.clear(); - mDecimator.dispose(); - mDecimator = null; - mFirstStage = null; - mStages = null; - } + Validate.isTrue(decimation <= 700); - @Override - public void receive( float i, float q ) + + List stageSizes = getPrimeFactors(decimation); + + for(int x = 0; x < stageSizes.size(); x++) { - mFirstStage.receive( i, q ); - } - - public void setListener( ComplexSampleListener listener ) - { - mDecimator.setListener( listener ); - } - } - - /** - * Single non-decimating CIC stage component. Uses a circular buffer and a - * running average internally to implement the stage so that stage size has - * essentially no impact on the computational requirements of the stage - */ - public class Stage implements ComplexSampleListener - { - protected ComplexSampleListener mListener; - - private float[] mISamples; - private float[] mQSamples; - - protected float mISum; - protected float mQSum; - - private int mSamplePointer = 0; - private int mSize; - - protected float mGain; - - public Stage() - { - } + DecimatingStage stage = new DecimatingStage(stageSizes.get(x), order); - public Stage( int size ) - { - mSize = size - 1; - - mISamples = new float[ mSize ]; - mQSamples = new float[ mSize ]; - - mGain = 1.0f / (float)size; - } - - public void dispose() - { - mListener = null; - } - - public void receive( float i, float q ) - { + mDecimatingStages.add(stage); + + if(x == 0) + { + /* Reference to first stage -- will receive all samples */ + mFirstDecimatingStage = stage; + } + else + { + /* Wire the current stage to the previous stage */ + mDecimatingStages.get(x - 1).setListener(stage); + } + } + + mOutput = new Output(48000, passFrequency, attenuation, windowType); + + mDecimatingStages.get(mDecimatingStages.size() - 1) + .setListener(mOutput); + } + + public void dispose() + { + for(DecimatingStage stage : mDecimatingStages) + { + stage.dispose(); + } + + mDecimatingStages.clear(); + mDecimatingStages = null; + mFirstDecimatingStage = null; + + mOutput.dispose(); + mOutput = null; + } + + /** + * Adds a listener to receive the output of this CIC decimation filter + */ + public void setListener(Listener listener) + { + mOutput.setListener(listener); + } + + /** + * Removes listener from output of this CIC decimation filter + */ + public void removeListener() + { + mOutput.removeListener(); + } + + /** + * Calculates the prime factors of the decimation rate up to a maximum + * decimation rate of 700. If you wish to have decimation rates higher + * than 700, then add additional prime factors to the PRIMES array. + * + * @param decimation - integral decimation rate + * @return - ordered list (smallest to largest) of prime factors + */ + public static List getPrimeFactors(int decimation) + { + ArrayList stages = new ArrayList(); + + int pointer = 0; + + while(decimation > 0 && pointer < PRIMES.length) + { + int prime = PRIMES[pointer]; + + if(decimation % prime == 0) + { + stages.add(prime); + + decimation /= prime; + } + else + { + pointer++; + } + } + + return stages; + } + + /** + * Primary input method for receiving sample arrays composed as I,Q,I,Q, etc. + */ + @Override + public void receive(ComplexBuffer buffer) + { + if(mFirstDecimatingStage != null) + { + float[] samples = buffer.getSamples(); + + for(int x = 0; x < samples.length; x += 2) + { + mFirstDecimatingStage.receive(samples[x], samples[x + 1]); + } + } + } + + /** + * Decimating stage combines multiple CIC stages with a decimator. The + * number of stages is indicated by the order value and the size indicates + * the decimation rate of this stage. + */ + public class DecimatingStage implements ComplexSampleListener + { + private ArrayList mStages = new ArrayList(); + private Stage mFirstStage; + private ComplexDecimator mDecimator; + + public DecimatingStage(int size, int order) + { + for(int x = 0; x < order; x++) + { + Stage stage; + + if(size == 2) + { + stage = new TwoStage(); + } + else + { + stage = new Stage(size); + } + + mStages.add(stage); + + if(x == 0) + { + mFirstStage = stage; + } + else + { + mStages.get(x - 1).setListener(stage); + } + } + + mDecimator = new ComplexDecimator(size); + + mStages.get(mStages.size() - 1).setListener(mDecimator); + } + + public void dispose() + { + for(Stage stage : mStages) + { + stage.dispose(); + } + + mStages.clear(); + mDecimator.dispose(); + mDecimator = null; + mFirstStage = null; + mStages = null; + } + + @Override + public void receive(float i, float q) + { + mFirstStage.receive(i, q); + } + + public void setListener(ComplexSampleListener listener) + { + mDecimator.setListener(listener); + } + } + + /** + * Single non-decimating CIC stage component. Uses a circular buffer and a + * running average internally to implement the stage so that stage size has + * essentially no impact on the computational requirements of the stage + */ + public class Stage implements ComplexSampleListener + { + protected ComplexSampleListener mListener; + + private float[] mISamples; + private float[] mQSamples; + + protected float mISum; + protected float mQSum; + + private int mSamplePointer = 0; + private int mSize; + + protected float mGain; + + public Stage() + { + } + + public Stage(int size) + { + mSize = size - 1; + + mISamples = new float[mSize]; + mQSamples = new float[mSize]; + + mGain = 1.0f / (float)size; + } + + public void dispose() + { + mListener = null; + } + + public void receive(float i, float q) + { /* Subtract the oldest sample and add back in the newest */ - mISum = mISum - mISamples[ mSamplePointer ] + i; - mQSum = mQSum - mQSamples[ mSamplePointer ] + q; + mISum = mISum - mISamples[mSamplePointer] + i; + mQSum = mQSum - mQSamples[mSamplePointer] + q; /* Overwrite the oldest sample with the newest */ - mISamples[ mSamplePointer ] = i; - mQSamples[ mSamplePointer ] = q; - - mSamplePointer++; - - if( mSamplePointer >= mSize ) - { - mSamplePointer = 0; - } + mISamples[mSamplePointer] = i; + mQSamples[mSamplePointer] = q; - if( mListener != null ) - { - mListener.receive( ( mISum * mGain ), ( mQSum * mGain ) ); - } - } - - public void setListener( ComplexSampleListener listener ) - { - mListener = listener; - } - } - - /** - * Size 2 stage that removes the unnecessary circular buffer management for - * a two-stage. - */ - public class TwoStage extends Stage - { - public TwoStage() - { - super(); - - mGain = 0.5f; - } - - public void receive( float i, float q ) - { - float iSum = mISum + i; - float qSum = mQSum + q; - - mISum = i; - mQSum = q; - - if( mListener != null ) - { - mListener.receive( ( iSum * mGain ), ( qSum * mGain ) ); - } - } - } - + mSamplePointer++; - /** - * Output adapter - applies gain correction and cleanup filter and broadcast - * to registered listener. - */ - public class Output implements ComplexSampleListener - { - /* Decimated output buffers will contain 1024 samples */ - private ComplexToComplexBufferAssembler mAssembler = - new ComplexToComplexBufferAssembler( 2048 ); + if(mSamplePointer >= mSize) + { + mSamplePointer = 0; + } - private ComplexFIRFilter_CB_CB mCleanupFilter; - private HalfBandFilter_CB_CB mHalfBandFilter = new HalfBandFilter_CB_CB( - Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 0.4f, false ); - - public Output( int outputSampleRate, int passFrequency, int attenuation, - WindowType windowType ) - { - mCleanupFilter = new ComplexFIRFilter_CB_CB( FilterFactory - .getCICCleanupFilter( outputSampleRate, - passFrequency, - attenuation, - windowType ), 0.4f ); - - mAssembler.setListener( mCleanupFilter ); - mCleanupFilter.setListener( mHalfBandFilter ); - } - - public void dispose() - { - mAssembler.dispose(); - mCleanupFilter.dispose(); - mHalfBandFilter.dispose(); - } - - /** - * Receiver method for the output adapter to receive a filtered, - * decimated sample, apply gain correction, apply cleanup filtering - * and output the sample values as a complex sample. - */ - @Override - public void receive( float inphase, float quadrature ) - { - mAssembler.receive( inphase, quadrature ); - } - - /** - * Adds a listener to receive output samples - */ - public void setListener( Listener listener ) - { - mHalfBandFilter.setListener( listener ); + if(mListener != null) + { + mListener.receive((mISum * mGain), (mQSum * mGain)); + } } - /** - * Removes the listener from receiving output samples - */ + public void setListener(ComplexSampleListener listener) + { + mListener = listener; + } + } + + /** + * Size 2 stage that removes the unnecessary circular buffer management for + * a two-stage. + */ + public class TwoStage extends Stage + { + public TwoStage() + { + super(); + + mGain = 0.5f; + } + + public void receive(float i, float q) + { + float iSum = mISum + i; + float qSum = mQSum + q; + + mISum = i; + mQSum = q; + + if(mListener != null) + { + mListener.receive((iSum * mGain), (qSum * mGain)); + } + } + } + + + /** + * Output adapter - applies gain correction and cleanup filter and broadcast + * to registered listener. + */ + public class Output implements ComplexSampleListener + { + /* Decimated output buffers will contain 1024 samples */ + private ComplexToComplexBufferAssembler mAssembler = + new ComplexToComplexBufferAssembler(2048); + + private ComplexFIRFilter_CB_CB mCleanupFilter; + private HalfBandFilter_CB_CB mHalfBandFilter = new HalfBandFilter_CB_CB( + Filters.FIR_HALF_BAND_31T_ONE_EIGHTH_FCO.getCoefficients(), 0.4f, false); + + public Output(int outputSampleRate, int passFrequency, int attenuation, + WindowType windowType) + { + mCleanupFilter = new ComplexFIRFilter_CB_CB(FilterFactory + .getCICCleanupFilter(outputSampleRate, + passFrequency, + attenuation, + windowType), 0.4f); + + //Bypassing the CIC cleanup filter for now + mAssembler.setListener(mHalfBandFilter); + +// mAssembler.setListener( mCleanupFilter ); +// mCleanupFilter.setListener( mHalfBandFilter ); + } + + public void dispose() + { + mAssembler.dispose(); + mCleanupFilter.dispose(); + mHalfBandFilter.dispose(); + } + + /** + * Receiver method for the output adapter to receive a filtered, + * decimated sample, apply gain correction, apply cleanup filtering + * and output the sample values as a complex sample. + */ + @Override + public void receive(float inphase, float quadrature) + { + mAssembler.receive(inphase, quadrature); + } + + /** + * Adds a listener to receive output samples + */ + public void setListener(Listener listener) + { + mHalfBandFilter.setListener(listener); + } + + /** + * Removes the listener from receiving output samples + */ public void removeListener() { - mHalfBandFilter.removeListener(); + mHalfBandFilter.removeListener(); } - } + } } From d510c3435644c96f9948db8b5f60da017728dc01 Mon Sep 17 00:00:00 2001 From: Denny Date: Sat, 11 Mar 2017 12:01:45 -0500 Subject: [PATCH 11/20] Adds frequency change event listener handler to LSM decoder. --- src/module/decode/p25/P25_LSMDecoder.java | 9 ++++++++- src/sample/Broadcaster.java | 5 +++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/module/decode/p25/P25_LSMDecoder.java b/src/module/decode/p25/P25_LSMDecoder.java index 94521323..db41a441 100644 --- a/src/module/decode/p25/P25_LSMDecoder.java +++ b/src/module/decode/p25/P25_LSMDecoder.java @@ -121,7 +121,14 @@ public class P25_LSMDecoder extends P25Decoder implements IComplexBufferListener @Override public Listener getFrequencyChangeListener() { - return null; + return new Listener() + { + @Override + public void receive(FrequencyChangeEvent frequencyChangeEvent) + { + //Ignored + } + }; } @Override diff --git a/src/sample/Broadcaster.java b/src/sample/Broadcaster.java index 4fc1f819..d4f9cce1 100644 --- a/src/sample/Broadcaster.java +++ b/src/sample/Broadcaster.java @@ -58,6 +58,11 @@ public class Broadcaster implements Listener public void addListener( Listener listener ) { + if(listener == null) + { + throw new IllegalArgumentException("Listener cannot be null"); + } + mListeners.add( listener ); } From 13464e01fc1b71b86db4eb27cd3b381249caf39b Mon Sep 17 00:00:00 2001 From: Denny Date: Sun, 12 Mar 2017 11:33:04 -0400 Subject: [PATCH 12/20] Updates build to 0.3.0 Beta 9 --- build/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.xml b/build/build.xml index d3dd6483..c6f7637b 100644 --- a/build/build.xml +++ b/build/build.xml @@ -7,7 +7,7 @@ - + From a2ab5bfa95937fa51cd8b5a973b434c56070c03e Mon Sep 17 00:00:00 2001 From: Denny Date: Thu, 23 Mar 2017 20:55:42 +0000 Subject: [PATCH 13/20] Resolves #230 - P25 Phase II TDMA frequency bands are now displayed in the channel details summary. --- src/module/decode/p25/P25DecoderState.java | 1 + .../p25/message/tsbk/osp/control/IdentifierUpdateTDMA.java | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/module/decode/p25/P25DecoderState.java b/src/module/decode/p25/P25DecoderState.java index dbd7e596..ccc66272 100644 --- a/src/module/decode/p25/P25DecoderState.java +++ b/src/module/decode/p25/P25DecoderState.java @@ -1557,6 +1557,7 @@ public class P25DecoderState extends DecoderState break; case IDENTIFIER_UPDATE_NON_VUHF: case IDENTIFIER_UPDATE_VHF_UHF_BANDS: + case IDENTIFIER_UPDATE_TDMA: IdentifierUpdate iu = (IdentifierUpdate)tsbk; if(!mBands.containsKey(iu.getIdentifier())) diff --git a/src/module/decode/p25/message/tsbk/osp/control/IdentifierUpdateTDMA.java b/src/module/decode/p25/message/tsbk/osp/control/IdentifierUpdateTDMA.java index 5a4ab33d..fc3b2d95 100644 --- a/src/module/decode/p25/message/tsbk/osp/control/IdentifierUpdateTDMA.java +++ b/src/module/decode/p25/message/tsbk/osp/control/IdentifierUpdateTDMA.java @@ -41,7 +41,7 @@ public class IdentifierUpdateTDMA extends IdentifierUpdate sb.append( " ID:" + getIdentifier() ); sb.append( " BASE:" + getBaseFrequency() ); - sb.append( " BW:" + getBandwidth() ); + sb.append( " BANDWIDTH:" + getBandwidth() ); sb.append( " SPACING:" + getChannelSpacing() ); sb.append( " OFFSET:" + getTransmitOffset() ); sb.append( " TYPE:" + getChannelType().toString() ); @@ -137,7 +137,7 @@ public class IdentifierUpdateTDMA extends IdentifierUpdate sb.append( mAccessType.name() ); sb.append( " BANDWIDTH:" + mBandwidth ); - sb.append( " SLOT/CARRIER:" + mSlotsPerCarrier ); + sb.append( " TIMESLOTS PER CARRIER:" + mSlotsPerCarrier ); sb.append( " VOCODER:" + mVocoder.name() ); return sb.toString(); From 5dbe0ecce3f99af29e8ae1ca2f0abfd406202c9d Mon Sep 17 00:00:00 2001 From: Denny Date: Thu, 23 Mar 2017 21:48:52 +0000 Subject: [PATCH 14/20] Resolves #229 - duplicate P25 TDMA call events. --- src/module/decode/p25/P25DecoderState.java | 51 ++++++++++++++-------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/src/module/decode/p25/P25DecoderState.java b/src/module/decode/p25/P25DecoderState.java index ccc66272..828be84c 100644 --- a/src/module/decode/p25/P25DecoderState.java +++ b/src/module/decode/p25/P25DecoderState.java @@ -315,6 +315,8 @@ public class P25DecoderState extends DecoderState */ public void registerCallEvent(P25CallEvent event) { + String channel = event.getChannel(); + if(mChannelCallMap.containsKey(event.getChannel())) { P25CallEvent previousEvent = mChannelCallMap.remove(event.getChannel()); @@ -2736,6 +2738,7 @@ public class P25DecoderState extends DecoderState else { StringBuilder details = new StringBuilder(); + details.append(pgvcg.isTDMAChannel() ? "TDMA " : ""); details.append(pgvcg.isEmergency() ? "EMERGENCY " : ""); details.append("PRI:").append(pgvcg.getPriority()); details.append(pgvcg.isEncryptedChannel() ? " ENCRYPTED" : ""); @@ -2743,7 +2746,7 @@ public class P25DecoderState extends DecoderState P25CallEvent event = new P25CallEvent.Builder(CallEventType.PATCH_GROUP_CALL) .aliasList(getAliasList()) - .channel(channel + (pgvcg.isTDMAChannel() ? " TDMA" : "")) + .channel(channel) .details(details.toString()) .frequency(pgvcg.getDownlinkFrequency()) .from(from) @@ -2776,8 +2779,9 @@ public class P25DecoderState extends DecoderState { P25CallEvent event = new P25CallEvent.Builder(CallEventType.PATCH_GROUP_CALL) .aliasList(getAliasList()) - .channel(channel + (gvcgu.isTDMAChannel1() ? " TDMA" : "")) - .details((gvcgu.isEncrypted() ? "ENCRYPTED " : "")) + .channel(channel) + .details((gvcgu.isTDMAChannel1() ? "TDMA " : "") + + (gvcgu.isEncrypted() ? "ENCRYPTED " : "")) .frequency(gvcgu.getDownlinkFrequency1()) .to(to) .build(); @@ -2802,8 +2806,9 @@ public class P25DecoderState extends DecoderState { P25CallEvent event = new P25CallEvent.Builder(CallEventType.PATCH_GROUP_CALL) .aliasList(getAliasList()) - .channel(channel2 + (gvcgu.isTDMAChannel2() ? " TDMA" : "")) - .details((gvcgu.isEncrypted() ? "ENCRYPTED " : "")) + .channel(channel2) + .details((gvcgu.isTDMAChannel2() ? "TDMA " : "") + + (gvcgu.isEncrypted() ? "ENCRYPTED " : "")) .frequency(gvcgu.getDownlinkFrequency2()) .to(to2) .build(); @@ -2867,6 +2872,7 @@ public class P25DecoderState extends DecoderState else { StringBuilder details = new StringBuilder(); + details.append(gdcg.isTDMAChannel() ? "TDMA " : ""); details.append(gdcg.isEmergency() ? "EMERGENCY " : ""); details.append("PRI:").append(gdcg.getPriority()).append(" "); details.append(gdcg.isEncryptedChannel() ? "ENCRYPTED " : ""); @@ -2874,7 +2880,7 @@ public class P25DecoderState extends DecoderState P25CallEvent event = new P25CallEvent.Builder(CallEventType.DATA_CALL) .aliasList(getAliasList()) - .channel(channel + (gdcg.isTDMAChannel() ? " TDMA" : "")) + .channel(channel) .details(details.toString()) .frequency(gdcg.getDownlinkFrequency()) .from(from) @@ -2905,6 +2911,7 @@ public class P25DecoderState extends DecoderState else { StringBuilder details = new StringBuilder(); + details.append(gvcg.isTDMAChannel() ? "TDMA " : ""); details.append(gvcg.isEmergency() ? "EMERGENCY " : ""); details.append("PRI:").append(gvcg.getPriority()).append(" "); details.append(gvcg.isEncryptedChannel() ? "ENCRYPTED " : ""); @@ -2912,7 +2919,7 @@ public class P25DecoderState extends DecoderState P25CallEvent event = new P25CallEvent.Builder(CallEventType.GROUP_CALL) .aliasList(getAliasList()) - .channel(channel + (gvcg.isTDMAChannel() ? " TDMA" : "")) + .channel(channel) .details(details.toString()) .frequency(gvcg.getDownlinkFrequency()) .from(from) @@ -2929,8 +2936,7 @@ public class P25DecoderState extends DecoderState } break; case GROUP_VOICE_CHANNEL_GRANT_UPDATE: - GroupVoiceChannelGrantUpdate gvcgu = - (GroupVoiceChannelGrantUpdate)message; + GroupVoiceChannelGrantUpdate gvcgu = (GroupVoiceChannelGrantUpdate)message; channel = gvcgu.getChannel1(); from = null; @@ -2943,6 +2949,7 @@ public class P25DecoderState extends DecoderState else { StringBuilder details = new StringBuilder(); + details.append(gvcgu.isTDMAChannel() ? "TDMA " : ""); details.append(gvcgu.isEmergency() ? "EMERGENCY " : ""); details.append("PRI:").append(gvcgu.getPriority()).append(" "); details.append(gvcgu.isEncryptedChannel() ? "ENCRYPTED " : ""); @@ -2950,7 +2957,7 @@ public class P25DecoderState extends DecoderState P25CallEvent event = new P25CallEvent.Builder(CallEventType.GROUP_CALL) .aliasList(getAliasList()) - .channel(gvcgu.getChannel1() + (gvcgu.isTDMAChannel1() ? " TDMA" : "")) + .channel(gvcgu.getChannel1()) .details(details.toString()) .frequency(gvcgu.getDownlinkFrequency1()) .from(from) @@ -2978,6 +2985,7 @@ public class P25DecoderState extends DecoderState else { StringBuilder details = new StringBuilder(); + details.append(gvcgu.isTDMAChannel() ? "TDMA " : ""); details.append(gvcgu.isEmergency() ? "EMERGENCY " : ""); details.append("PRI:").append(gvcgu.getPriority()).append(" "); details.append(gvcgu.isEncryptedChannel() ? "ENCRYPTED " : ""); @@ -2985,7 +2993,7 @@ public class P25DecoderState extends DecoderState P25CallEvent event2 = new P25CallEvent.Builder(CallEventType.GROUP_CALL) .aliasList(getAliasList()) - .channel(gvcgu.getChannel2() + (gvcgu.isTDMAChannel2() ? " TDMA" : "")) + .channel(gvcgu.getChannel2()) .details(details.toString()) .frequency(gvcgu.getDownlinkFrequency2()) .to(gvcgu.getGroupAddress2()) @@ -3017,6 +3025,7 @@ public class P25DecoderState extends DecoderState else { StringBuilder details = new StringBuilder(); + details.append(gvcgue.isTDMAChannel() ? "TDMA " : ""); details.append(gvcgue.isEmergency() ? "EMERGENCY " : ""); details.append("PRI:").append(gvcgue.getPriority()).append(" "); details.append(gvcgue.isEncryptedChannel() ? "ENCRYPTED " : ""); @@ -3024,7 +3033,7 @@ public class P25DecoderState extends DecoderState P25CallEvent event = new P25CallEvent.Builder(CallEventType.GROUP_CALL) .aliasList(getAliasList()) - .channel(channel + (gvcgue.isTDMAChannel() ? " TDMA" : "")) + .channel(channel) .details(details.toString()) .frequency(gvcgue.getDownlinkFrequency()) .from(from) @@ -3054,6 +3063,7 @@ public class P25DecoderState extends DecoderState else { StringBuilder details = new StringBuilder(); + details.append(idcg.isTDMAChannel() ? "TDMA " : ""); details.append(idcg.isEmergency() ? "EMERGENCY " : ""); details.append("PRI:").append(idcg.getPriority()).append(" "); details.append(idcg.isEncryptedChannel() ? "ENCRYPTED " : ""); @@ -3061,7 +3071,7 @@ public class P25DecoderState extends DecoderState P25CallEvent event = new P25CallEvent.Builder(CallEventType.DATA_CALL) .aliasList(getAliasList()) - .channel(channel + (idcg.isTDMAChannel() ? " TDMA" : "")) + .channel(channel) .details(details.toString()) .frequency(idcg.getDownlinkFrequency()) .from(from) @@ -3091,13 +3101,14 @@ public class P25DecoderState extends DecoderState else { StringBuilder details = new StringBuilder(); + details.append(sdcg.isTDMAChannel() ? "TDMA " : ""); details.append(sdcg.isEmergency() ? "EMERGENCY " : ""); details.append(sdcg.isEncryptedChannel() ? "ENCRYPTED " : ""); details.append(sdcg.getSessionMode().name()); P25CallEvent event = new P25CallEvent.Builder(CallEventType.DATA_CALL) .aliasList(getAliasList()) - .channel(channel + (sdcg.isTDMAChannel() ? " TDMA" : "")) + .channel(channel) .details(details.toString()) .frequency(sdcg.getDownlinkFrequency()) .from(from) @@ -3129,6 +3140,7 @@ public class P25DecoderState extends DecoderState else { StringBuilder details = new StringBuilder(); + details.append(tivcg.isTDMAChannel() ? "TDMA " : ""); details.append(tivcg.isEmergency() ? "EMERGENCY " : ""); details.append("PRI:").append(tivcg.getPriority()).append(" "); details.append(tivcg.isEncryptedChannel() ? "ENCRYPTED " : ""); @@ -3138,7 +3150,7 @@ public class P25DecoderState extends DecoderState P25CallEvent event = new P25CallEvent.Builder( CallEventType.TELEPHONE_INTERCONNECT) .aliasList(getAliasList()) - .channel(channel + (tivcg.isTDMAChannel() ? " TDMA" : "")) + .channel(channel) .details(details.toString()) .frequency(tivcg.getDownlinkFrequency()) .from(from) @@ -3171,6 +3183,7 @@ public class P25DecoderState extends DecoderState else { StringBuilder details = new StringBuilder(); + details.append(tivcgu.isTDMAChannel() ? "TDMA " : ""); details.append(tivcgu.isEmergency() ? "EMERGENCY " : ""); details.append("PRI:").append(tivcgu.getPriority()).append(" "); details.append(tivcgu.isEncryptedChannel() ? "ENCRYPTED " : ""); @@ -3180,7 +3193,7 @@ public class P25DecoderState extends DecoderState P25CallEvent event = new P25CallEvent.Builder( CallEventType.TELEPHONE_INTERCONNECT) .aliasList(getAliasList()) - .channel(channel + (tivcgu.isTDMAChannel() ? " TDMA" : "")) + .channel(channel) .details(details.toString()) .frequency(tivcgu.getDownlinkFrequency()) .from(from) @@ -3210,6 +3223,7 @@ public class P25DecoderState extends DecoderState else { StringBuilder details = new StringBuilder(); + details.append(uuvcg.isTDMAChannel() ? "TDMA " : ""); details.append(uuvcg.isEmergency() ? "EMERGENCY " : ""); details.append("PRI:").append(uuvcg.getPriority()).append(" "); details.append(uuvcg.isEncryptedChannel() ? " ENCRYPTED " : ""); @@ -3218,7 +3232,7 @@ public class P25DecoderState extends DecoderState P25CallEvent event = new P25CallEvent.Builder( CallEventType.UNIT_TO_UNIT_CALL) .aliasList(getAliasList()) - .channel(channel + (uuvcg.isTDMAChannel() ? " TDMA" : "")) + .channel(channel) .details(details.toString()) .frequency(uuvcg.getDownlinkFrequency()) .from(from) @@ -3248,6 +3262,7 @@ public class P25DecoderState extends DecoderState else { StringBuilder details = new StringBuilder(); + details.append(uuvcgu.isTDMAChannel() ? "TDMA " : ""); details.append(uuvcgu.isEmergency() ? "EMERGENCY " : ""); details.append("PRI:").append(uuvcgu.getPriority()).append(" "); details.append(uuvcgu.isEncryptedChannel() ? "ENCRYPTED " : ""); @@ -3256,7 +3271,7 @@ public class P25DecoderState extends DecoderState P25CallEvent event = new P25CallEvent.Builder( CallEventType.UNIT_TO_UNIT_CALL) .aliasList(getAliasList()) - .channel(channel + (uuvcgu.isTDMAChannel() ? " TDMA" : "")) + .channel(channel) .details(details.toString()) .frequency(uuvcgu.getDownlinkFrequency()) .from(from) From 5f2ce2a9f2a40e9df300a5e548fbd7bdb6991c07 Mon Sep 17 00:00:00 2001 From: Denny Date: Thu, 30 Mar 2017 19:18:39 -0400 Subject: [PATCH 15/20] Resolves #235 - fixes error on constructing a LoJack decoder state. --- src/module/decode/lj1200/LJ1200DecoderState.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/module/decode/lj1200/LJ1200DecoderState.java b/src/module/decode/lj1200/LJ1200DecoderState.java index e54e6729..5382f034 100644 --- a/src/module/decode/lj1200/LJ1200DecoderState.java +++ b/src/module/decode/lj1200/LJ1200DecoderState.java @@ -54,7 +54,7 @@ public class LJ1200DecoderState extends DecoderState super(aliasList); mAddressAttribute = new AliasedStringAttributeMonitor(Attribute.SECONDARY_ADDRESS_TO, - getAttributeChangeRequestListener(), getAliasList(), AliasIDType.LOJACK); + getAttributeChangeRequestListener(), getAliasList(), AliasIDType.SITE); } @Override From dab6df7836a62c82a2ed0b23ebcabab000643735 Mon Sep 17 00:00:00 2001 From: Denny Date: Thu, 30 Mar 2017 19:26:35 -0400 Subject: [PATCH 16/20] Resolves #228 - traffic channel manager now allocates traffic channels even when the alias is marked as do not monitor --- .../traffic/TrafficChannelManager.java | 47 +------------------ src/module/decode/DecoderFactory.java | 4 +- 2 files changed, 3 insertions(+), 48 deletions(-) diff --git a/src/channel/traffic/TrafficChannelManager.java b/src/channel/traffic/TrafficChannelManager.java index 16cdec10..c2568de8 100644 --- a/src/channel/traffic/TrafficChannelManager.java +++ b/src/channel/traffic/TrafficChannelManager.java @@ -63,13 +63,11 @@ public class TrafficChannelManager extends Module implements ICallEventProvider, private Listener mCallEventListener; private ChannelModel mChannelModel; - private ChannelProcessingManager mChannelProcessingManager; private DecodeConfiguration mDecodeConfiguration; private RecordConfiguration mRecordConfiguration; private String mSystem; private String mSite; private String mAliasListName; - private CallEvent mPreviousDoNotMonitorCallEvent; /** * Monitors call events and allocates traffic decoder channels in response @@ -77,7 +75,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider, * traffic channel allocations. * * @param channelModel containing channels currently in use - * @param channelProcessingManager * @param decodeConfiguration - decoder configuration to use for each * traffic channel allocation. * @param recordConfiguration - recording options for each traffic channel @@ -88,7 +85,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider, * in the pool */ public TrafficChannelManager(ChannelModel channelModel, - ChannelProcessingManager channelProcessingManager, DecodeConfiguration decodeConfiguration, RecordConfiguration recordConfiguration, String system, @@ -97,7 +93,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider, int trafficChannelPoolSize) { mChannelModel = channelModel; - mChannelProcessingManager = channelProcessingManager; mDecodeConfiguration = decodeConfiguration; mRecordConfiguration = recordConfiguration; mSystem = system; @@ -120,7 +115,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider, mCallEventListener = null; mDecodeConfiguration = null; - mPreviousDoNotMonitorCallEvent = null; } /** @@ -197,21 +191,7 @@ public class TrafficChannelManager extends Module implements ICallEventProvider, long frequency = callEvent.getFrequency(); - /* Check the from/to aliases for do not monitor priority */ - if(isDoNotMonitor(callEvent)) - { - callEvent.setCallEventType(CallEventType.CALL_DO_NOT_MONITOR); - - if(isSameCallEvent(mPreviousDoNotMonitorCallEvent, callEvent)) - { - return; - } - else - { - mPreviousDoNotMonitorCallEvent = callEvent; - } - } - else if(frequency > 0) + if(frequency > 0) { Channel channel = getChannel(callEvent.getChannel(), new TunerChannel(Type.TRAFFIC, frequency, mDecodeConfiguration.getDecoderType().getChannelBandwidth())); @@ -330,31 +310,6 @@ public class TrafficChannelManager extends Module implements ICallEventProvider, return false; } - /** - * Checks for aliases that contain a do not follow/process priority. - * - * @param event - * @return - true if there is an alias that has a do not process alias id - */ - private boolean isDoNotMonitor(CallEvent event) - { - Alias to = event.getToIDAlias(); - - if(to != null && to.hasCallPriority() && to.getCallPriority() == Priority.DO_NOT_MONITOR) - { - return true; - } - - Alias from = event.getFromIDAlias(); - - if(from != null && from.hasCallPriority() && from.getCallPriority() == Priority.DO_NOT_MONITOR) - { - return true; - } - - return false; - } - @Override public Listener getDecoderStateListener() { diff --git a/src/module/decode/DecoderFactory.java b/src/module/decode/DecoderFactory.java index cce17e65..691f1055 100644 --- a/src/module/decode/DecoderFactory.java +++ b/src/module/decode/DecoderFactory.java @@ -222,7 +222,7 @@ public class DecoderFactory if(channelType == ChannelType.STANDARD) { - modules.add(new TrafficChannelManager(channelModel, channelProcessingManager, decodeConfig, + modules.add(new TrafficChannelManager(channelModel, decodeConfig, channel.getRecordConfiguration(), channel.getSystem(), channel.getSite(), (aliasList != null ? aliasList.getName() : null), mptConfig.getTrafficChannelPoolSize())); } @@ -263,7 +263,7 @@ public class DecoderFactory if(channelType == ChannelType.STANDARD) { - modules.add(new TrafficChannelManager(channelModel, channelProcessingManager, decodeConfig, + modules.add(new TrafficChannelManager(channelModel, decodeConfig, channel.getRecordConfiguration(), channel.getSystem(), channel.getSite(), (aliasList != null ? aliasList.getName() : null), p25Config.getTrafficChannelPoolSize())); } From 5fe8ccd478f88ccacb406ecd8490a75db90ce939 Mon Sep 17 00:00:00 2001 From: Denny Date: Thu, 30 Mar 2017 19:52:35 -0400 Subject: [PATCH 17/20] Resolves #234 - change the order of assigning an icon and alias text to the audio panel in an attempt to prevent the audio channel panel from wrapping to the next line when the text is too long to display. --- src/audio/AudioChannelPanel.java | 18 +++++++++--------- src/audio/AudioChannelsPanel.java | 1 - 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/audio/AudioChannelPanel.java b/src/audio/AudioChannelPanel.java index 2181d093..93bf084c 100644 --- a/src/audio/AudioChannelPanel.java +++ b/src/audio/AudioChannelPanel.java @@ -90,7 +90,8 @@ public class AudioChannelPanel extends JPanel private void init() { - setLayout(new MigLayout("align center center, insets 0 0 0 0", "[][][align right]0[grow,fill]", "")); + setLayout(new MigLayout("align center center, insets 0 0 0 0", + "[][][align right]0[grow,fill]", "")); setBackground(mBackgroundColor); mMutedLabel.setFont(mFont); @@ -159,30 +160,29 @@ public class AudioChannelPanel extends JPanel * does not occur on the Swing event thread -- wrap any calls to this * method with an event thread call. */ - private void updateLabel(JLabel valueLabel, String value, JLabel aliasLabel, Alias alias) + private void updateLabel(JLabel textLabel, String value, JLabel iconLabel, Alias alias) { if(value != null) { if(alias != null) { - valueLabel.setText(alias.getName()); - aliasLabel.setIcon(mIconManager.getIcon(alias.getIconName(), 18)); + iconLabel.setIcon(mIconManager.getIcon(alias.getIconName(), 18)); + textLabel.setText(alias.getName()); } else { - valueLabel.setText(value); - aliasLabel.setIcon(null); + iconLabel.setIcon(null); + textLabel.setText(value); } } else { - valueLabel.setText("-----"); - aliasLabel.setIcon(null); + iconLabel.setIcon(null); + textLabel.setText("-----"); } } - /** * Processes audio metadata to update this panel's display values */ diff --git a/src/audio/AudioChannelsPanel.java b/src/audio/AudioChannelsPanel.java index b2d94fb6..ac47c020 100644 --- a/src/audio/AudioChannelsPanel.java +++ b/src/audio/AudioChannelsPanel.java @@ -33,7 +33,6 @@ public class AudioChannelsPanel extends JPanel public AudioChannelsPanel(IconManager iconManager, SettingsManager settingsManager, IAudioController controller) { -// setLayout(new MigLayout("insets 0 0 0 0", "[][grow,fill][][grow,fill]", "[grow,fill]")); setLayout(new MigLayout("insets 0 0 0 0", "[][sg,grow,fill][][sg,grow,fill]", "[grow,fill]")); setBackground(Color.BLACK); From 25f71a76c48309b7b0733f30844fa5eb7c023544 Mon Sep 17 00:00:00 2001 From: Denny Date: Thu, 30 Mar 2017 20:03:48 -0400 Subject: [PATCH 18/20] Resolves #234 - hard codes alias name truncation when name exceeds 33 characters in length. --- src/audio/AudioChannelPanel.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/audio/AudioChannelPanel.java b/src/audio/AudioChannelPanel.java index 93bf084c..bae38200 100644 --- a/src/audio/AudioChannelPanel.java +++ b/src/audio/AudioChannelPanel.java @@ -168,7 +168,18 @@ public class AudioChannelPanel extends JPanel if(alias != null) { iconLabel.setIcon(mIconManager.getIcon(alias.getIconName(), 18)); - textLabel.setText(alias.getName()); + + String aliasName = alias.getName(); + + //Truncate label if length exceeds 33 characters - hack! + if(aliasName.length() > 33) + { + textLabel.setText(aliasName.substring(0,30) + "..."); + } + else + { + textLabel.setText(aliasName); + } } else { From a628800a71e0288dc61b74d09b598d2ef1895bf8 Mon Sep 17 00:00:00 2001 From: Denny Date: Thu, 30 Mar 2017 21:12:06 -0400 Subject: [PATCH 19/20] Resolves #234 - updates the miglayout layout manager to allow jlabel text strings to be automatically truncated (with ellipses) when the alias name is too long. --- src/audio/AudioChannelPanel.java | 2 +- src/audio/AudioChannelsPanel.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/audio/AudioChannelPanel.java b/src/audio/AudioChannelPanel.java index bae38200..8d659173 100644 --- a/src/audio/AudioChannelPanel.java +++ b/src/audio/AudioChannelPanel.java @@ -110,7 +110,7 @@ public class AudioChannelPanel extends JPanel mTo.setFont(mFont); mTo.setForeground(mValueColor); - add(mTo); + add(mTo,"wmin 10lp"); } @Override diff --git a/src/audio/AudioChannelsPanel.java b/src/audio/AudioChannelsPanel.java index ac47c020..ebfc1b9b 100644 --- a/src/audio/AudioChannelsPanel.java +++ b/src/audio/AudioChannelsPanel.java @@ -33,7 +33,7 @@ public class AudioChannelsPanel extends JPanel public AudioChannelsPanel(IconManager iconManager, SettingsManager settingsManager, IAudioController controller) { - setLayout(new MigLayout("insets 0 0 0 0", "[][sg,grow,fill][][sg,grow,fill]", "[grow,fill]")); + setLayout(new MigLayout("insets 0 0 0 0", "[][sg abc,grow,fill][][sg abc,grow,fill]", "[grow,fill]")); setBackground(Color.BLACK); @@ -43,7 +43,7 @@ public class AudioChannelsPanel extends JPanel for(int x = 0; x < outputs.size(); x++) { - add(new AudioChannelPanel(iconManager, settingsManager, outputs.get(x)), "growx"); + add(new AudioChannelPanel(iconManager, settingsManager, outputs.get(x))); if(x < outputs.size() - 1) { From fbc6e508bd1132d9030aefc698672a0c5180472c Mon Sep 17 00:00:00 2001 From: Denny Date: Fri, 14 Apr 2017 07:46:18 -0400 Subject: [PATCH 20/20] Increments build to 0.3.0-beta10 --- build/build.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.xml b/build/build.xml index c6f7637b..86210e2b 100644 --- a/build/build.xml +++ b/build/build.xml @@ -7,7 +7,7 @@ - +