diff --git a/src/controller/ConfigurationControllerModel.java b/src/controller/ConfigurationControllerModel.java index 982ccaa1..ab990a0b 100644 --- a/src/controller/ConfigurationControllerModel.java +++ b/src/controller/ConfigurationControllerModel.java @@ -31,6 +31,7 @@ import javax.swing.tree.TreePath; import map.IconManager; import playlist.PlaylistNode; import source.config.SourceConfigTuner; +import source.recording.RecordingGroupNode; import source.tuner.Tuner; import source.tuner.TunerGroupNode; import source.tuner.TunerNode; @@ -61,8 +62,8 @@ public class ConfigurationControllerModel extends DefaultTreeModel private PlaylistNode mPlaylistNode; - private TunerGroupNode mTunerGroupNode; + private RecordingGroupNode mRecordingGroupNode; private IconManager mIconManagerFrame; @@ -114,17 +115,24 @@ public class ConfigurationControllerModel extends DefaultTreeModel public void init() { /** - * Add the root tuner group node + * Add the tuner group node */ mTunerGroupNode = new TunerGroupNode(); insertNodeInto( mTunerGroupNode, (MutableTreeNode)root, 0 ); addTuners( mTunerGroupNode ); + /** + * Add the recording group node + */ + mRecordingGroupNode = new RecordingGroupNode(); + insertNodeInto( mRecordingGroupNode, (MutableTreeNode)root, 1 ); + mRecordingGroupNode.loadRecordings(); + /** * Add the playlist node */ mPlaylistNode = new PlaylistNode(); - insertNodeInto( mPlaylistNode, (MutableTreeNode)root, 1 ); + insertNodeInto( mPlaylistNode, (MutableTreeNode)root, 2 ); mPlaylistNode.loadPlaylist(); } diff --git a/src/controller/ResourceManager.java b/src/controller/ResourceManager.java index 3075dddb..33caf291 100644 --- a/src/controller/ResourceManager.java +++ b/src/controller/ResourceManager.java @@ -22,6 +22,7 @@ import playlist.PlaylistManager; import record.RecorderManager; import settings.SettingsManager; import source.SourceManager; +import source.recording.RecordingSourceManager; import source.tuner.TunerManager; import controller.channel.ChannelManager; import eventlog.EventLogManager; @@ -35,6 +36,7 @@ public class ResourceManager private EventLogManager mEventLogManager; private PlaylistManager mPlaylistManager; private RecorderManager mRecorderManager; + private RecordingSourceManager mRecordingSourceManager; private SettingsManager mSettingsManager; private SourceManager mSourceManager; private TunerManager mTunerManager; @@ -66,6 +68,7 @@ public class ResourceManager */ mChannelManager = new ChannelManager( this ); mSourceManager = new SourceManager( this ); + mRecordingSourceManager = new RecordingSourceManager( this ); mTunerManager = new TunerManager( this ); mController = new ConfigurationControllerModel( this ); mMapService = new MapService( this ); @@ -146,6 +149,16 @@ public class ResourceManager { mSourceManager = sourceManager; } + + public RecordingSourceManager getRecordingSourceManager() + { + return mRecordingSourceManager; + } + + public void setRecordingSourceManager( RecordingSourceManager manager ) + { + mRecordingSourceManager = manager; + } public ChannelManager getChannelManager() { diff --git a/src/controller/channel/Channel.java b/src/controller/channel/Channel.java index 8cde4d5c..ce35eef4 100644 --- a/src/controller/channel/Channel.java +++ b/src/controller/channel/Channel.java @@ -40,6 +40,7 @@ import record.config.RecordConfiguration; import sample.Listener; import source.SourceType; import source.config.SourceConfigFactory; +import source.config.SourceConfigRecording; import source.config.SourceConfigTuner; import source.config.SourceConfiguration; import source.tuner.TunerChannel; @@ -344,6 +345,14 @@ public class Channel extends Configuration retVal = new TunerChannel( Type.LOCKED, config.getFrequency(), mDecodeConfiguration.getDecoderType().getChannelBandwidth() ); } + else if( mSourceConfiguration.getSourceType() == SourceType.RECORDING ) + { + SourceConfigRecording config = + (SourceConfigRecording)mSourceConfiguration; + + retVal = new TunerChannel( Type.LOCKED, config.getFrequency(), + mDecodeConfiguration.getDecoderType().getChannelBandwidth() ); + } return retVal; } diff --git a/src/controller/channel/ProcessingChain.java b/src/controller/channel/ProcessingChain.java index 5c2f019c..d94b2b62 100644 --- a/src/controller/channel/ProcessingChain.java +++ b/src/controller/channel/ProcessingChain.java @@ -41,6 +41,7 @@ import source.FloatArraySource; import source.FloatSource; import source.Source; import source.Source.SampleType; +import source.SourceException; import source.config.SourceConfigMixer; import source.tuner.Tuner; import source.tuner.TunerChannelSource; @@ -256,8 +257,18 @@ public class ProcessingChain implements Listener /* Get a new source if needed and start the sample processor thread */ if( mChannel.getEnabled() && mResourceManager != null ) { - mSource = mResourceManager.getSourceManager() - .getSource( ProcessingChain.this ); + try + { + mSource = mResourceManager.getSourceManager() + .getSource( ProcessingChain.this ); + } + catch ( SourceException e ) + { + Log.error( "Couldn't obtain source for processing chain [" + + toString() + "] - " + e.getLocalizedMessage() ); + + mSource = null; + } if( mSource != null ) { diff --git a/src/decode/DecoderType.java b/src/decode/DecoderType.java index 98179803..3695bada 100644 --- a/src/decode/DecoderType.java +++ b/src/decode/DecoderType.java @@ -31,7 +31,8 @@ public enum DecoderType MDC1200( "MDC1200", "MDC1200", "images/fm", 12500 ), MPT1327( "MPT1327", "MPT1327", "images/mpt1327", 12500 ), NBFM( "NBFM", "NBFM", "images/fm", 12500 ), - PASSPORT( "Passport", "Passport", "images/passport", 12500 ); + PASSPORT( "Passport", "Passport", "images/passport", 12500 ), + P25( "P25", "P25", "images/p25", 12500 ); private String mDisplayString; private String mShortDisplayString; @@ -68,7 +69,8 @@ public enum DecoderType DecoderType.LTR_NET, DecoderType.MDC1200, DecoderType.MPT1327, - DecoderType.PASSPORT ); + DecoderType.PASSPORT, + DecoderType.P25 ); } /** diff --git a/src/decode/p25/P25Decoder.java b/src/decode/p25/P25Decoder.java new file mode 100644 index 00000000..9942d993 --- /dev/null +++ b/src/decode/p25/P25Decoder.java @@ -0,0 +1,144 @@ +/******************************************************************************* + * SDR Trunk + * Copyright (C) 2014 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + ******************************************************************************/ +package decode.p25; + +import instrument.Instrumentable; +import instrument.tap.Tap; +import instrument.tap.stream.FloatTap; + +import java.util.ArrayList; +import java.util.List; + +import source.Source.SampleType; +import alias.AliasList; +import decode.Decoder; +import decode.DecoderType; +import dsp.filter.DCRemovalFilter2; +import dsp.fsk.C4FMDecoder; +import dsp.nbfm.FilteringNBFMDemodulator; + +public class P25Decoder extends Decoder implements Instrumentable +{ + /** + * This value determines how quickly the DC remove filter responds to + * changes in frequency. + */ + private static final double sDC_REMOVAL_RATIO = 0.000003; + + private final String TAP_DEMOD_TO_DECODER = "Demodulated Audio <> C4FM Decoder"; + + private FilteringNBFMDemodulator mDemodulator; + private DCRemovalFilter2 mDCRemovalFilter; + private C4FMDecoder mC4FMDecoder; + private AliasList mAliasList; + + private ArrayList mTaps; + + public P25Decoder( SampleType sampleType, AliasList aliasList ) + { + super( sampleType ); + + mAliasList = aliasList; + + /** + * Only setup a demod chain if we're receiving complex samples. If + * we're receiving demodulated samples, they'll be handled the same + * way as we handle the output of the demodulator. + */ + if( mSourceSampleType == SampleType.COMPLEX ) + { + /** + * The Decoder super class is both a Complex listener and a float + * listener. So, we add the demod to listen to the incoming + * quadrature samples, and we wire the output of the demod right + * back to this class, so we can receive the demodulated output + * to process + */ + mDemodulator = new FilteringNBFMDemodulator(); + this.addComplexListener( mDemodulator ); + + /** + * Remove the DC component that is present when we're mistuned + */ + mDCRemovalFilter = new DCRemovalFilter2( sDC_REMOVAL_RATIO ); + mDemodulator.addListener( mDCRemovalFilter ); + + /** + * Route the demodulated, filtered samples back to this class to send + * to all registered listeners + */ + mDCRemovalFilter.setListener( this.getFloatReceiver() ); + + } + + mC4FMDecoder = new C4FMDecoder(); + addFloatListener( mC4FMDecoder ); + } + + @Override + public DecoderType getType() + { + return DecoderType.NBFM; + } + + @Override + public List getTaps() + { + if( mTaps == null ) + { + mTaps = new ArrayList(); + mTaps.add( new FloatTap( TAP_DEMOD_TO_DECODER, 0, 1.0f ) ); + + mTaps.addAll( mC4FMDecoder.getTaps() ); + } + + return mTaps; + } + + @Override + public void addTap( Tap tap ) + { + mC4FMDecoder.addTap( tap ); + + switch( tap.getName() ) + { + case TAP_DEMOD_TO_DECODER: + FloatTap demodTap = (FloatTap)tap; + removeFloatListener( mC4FMDecoder ); + addFloatListener( demodTap ); + demodTap.setListener( mC4FMDecoder ); + break; + } + } + + @Override + public void removeTap( Tap tap ) + { + mC4FMDecoder.removeTap( tap ); + + switch( tap.getName() ) + { + case TAP_DEMOD_TO_DECODER: + FloatTap demodTap = (FloatTap)tap; + removeFloatListener( demodTap ); + demodTap.removeListener( mC4FMDecoder ); + addFloatListener( mC4FMDecoder ); + break; + } + } +} diff --git a/src/dsp/fsk/C4FMDecoder.java b/src/dsp/fsk/C4FMDecoder.java new file mode 100644 index 00000000..1bbd4974 --- /dev/null +++ b/src/dsp/fsk/C4FMDecoder.java @@ -0,0 +1,66 @@ +package dsp.fsk; + +import instrument.Instrumentable; +import instrument.tap.Tap; +import instrument.tap.stream.FloatTap; + +import java.util.ArrayList; +import java.util.List; + +import sample.Listener; + +public class C4FMDecoder implements Listener, Instrumentable +{ + private final String TAP_C4FM_INPUT = "C4FM Input"; + private ArrayList mTaps; + private FloatTap mInputTap; + + public C4FMDecoder() + { + } + + @Override + public List getTaps() + { + if( mTaps == null ) + { + mTaps = new ArrayList(); + + FloatTap floatTap = new FloatTap( TAP_C4FM_INPUT, 0, 1.0f ); + mTaps.add( floatTap ); + } + + return mTaps; + } + + @Override + public void addTap( Tap tap ) + { + switch( tap.getName() ) + { + case TAP_C4FM_INPUT: + mInputTap = (FloatTap)tap; + break; + } + } + + @Override + public void removeTap( Tap tap ) + { + switch( tap.getName() ) + { + case TAP_C4FM_INPUT: + mInputTap = null; + break; + } + } + + @Override + public void receive( Float t ) + { + if( mInputTap != null ) + { + mInputTap.receive( t ); + } + } +} diff --git a/src/instrument/gui/DecoderViewFrame.java b/src/instrument/gui/DecoderViewFrame.java index 10609409..969cab91 100644 --- a/src/instrument/gui/DecoderViewFrame.java +++ b/src/instrument/gui/DecoderViewFrame.java @@ -19,9 +19,13 @@ package instrument.gui; import instrument.Instrumentable; import instrument.tap.Tap; +import instrument.tap.TapViewPanel; import instrument.tap.stream.BinaryTap; +import instrument.tap.stream.BinaryTapViewPanel; import instrument.tap.stream.FloatTap; +import instrument.tap.stream.FloatTapViewPanel; import instrument.tap.stream.SymbolEventTap; +import instrument.tap.stream.SymbolEventTapViewPanel; import java.awt.Dimension; import java.awt.event.ActionEvent; @@ -51,7 +55,9 @@ import decode.Decoder; public class DecoderViewFrame extends JInternalFrame implements PositionListener, Listener { - private Decoder mDecoder; + private static final long serialVersionUID = 1L; + + private Decoder mDecoder; private WaveSource mWaveSource; private HashMap mPanelMap = diff --git a/src/instrument/gui/WaveSourceFrame.java b/src/instrument/gui/WaveSourceFrame.java index aec3b7b9..a39eccda 100644 --- a/src/instrument/gui/WaveSourceFrame.java +++ b/src/instrument/gui/WaveSourceFrame.java @@ -46,6 +46,7 @@ import decode.fleetsync2.Fleetsync2Decoder; import decode.ltrnet.LTRNetDecoder; import decode.mdc1200.MDCDecoder; import decode.mpt1327.MPT1327Decoder; +import decode.p25.P25Decoder; import decode.passport.PassportDecoder; public class WaveSourceFrame extends JInternalFrame implements PositionListener @@ -146,6 +147,10 @@ public class WaveSourceFrame extends JInternalFrame implements PositionListener break; case PASSPORT: decoder = new PassportDecoder( SampleType.FLOAT, null ); + break; + case P25: + decoder = new P25Decoder( SampleType.FLOAT, null ); + break; } if( decoder != null ) diff --git a/src/instrument/gui/TapViewPanel.java b/src/instrument/tap/TapViewPanel.java similarity index 98% rename from src/instrument/gui/TapViewPanel.java rename to src/instrument/tap/TapViewPanel.java index ad5e58af..8020cebf 100644 --- a/src/instrument/gui/TapViewPanel.java +++ b/src/instrument/tap/TapViewPanel.java @@ -15,7 +15,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see ******************************************************************************/ -package instrument.gui; +package instrument.tap; + +import instrument.gui.SampleModel; import java.awt.Color; import java.awt.Dimension; diff --git a/src/instrument/gui/BinaryTapViewPanel.java b/src/instrument/tap/stream/BinaryTapViewPanel.java similarity index 97% rename from src/instrument/gui/BinaryTapViewPanel.java rename to src/instrument/tap/stream/BinaryTapViewPanel.java index b5d8f9bf..913d12bf 100644 --- a/src/instrument/gui/BinaryTapViewPanel.java +++ b/src/instrument/tap/stream/BinaryTapViewPanel.java @@ -15,10 +15,11 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see ******************************************************************************/ -package instrument.gui; +package instrument.tap.stream; +import instrument.gui.SampleModel; import instrument.tap.Tap; -import instrument.tap.stream.BinaryTap; +import instrument.tap.TapViewPanel; import java.awt.Color; import java.awt.FontMetrics; diff --git a/src/instrument/tap/stream/FloatTapPhaseViewPanel.java b/src/instrument/tap/stream/FloatTapPhaseViewPanel.java new file mode 100644 index 00000000..84860735 --- /dev/null +++ b/src/instrument/tap/stream/FloatTapPhaseViewPanel.java @@ -0,0 +1,128 @@ +/******************************************************************************* + * SDR Trunk + * Copyright (C) 2014 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + ******************************************************************************/ +package instrument.tap.stream; + +import instrument.gui.SampleModel; +import instrument.tap.Tap; +import instrument.tap.TapViewPanel; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.geom.Path2D; +import java.awt.geom.Point2D; +import java.util.List; +import java.util.Observable; + +import log.Log; + +public class FloatTapPhaseViewPanel extends TapViewPanel +{ + private static final long serialVersionUID = 1L; + private Tap mTap; + private List mSamples; + private int mSampleCount; + + public FloatTapPhaseViewPanel( FloatTap tap ) + { + super( new SampleModel(), tap.getName() ); + + mTap = tap; + mTap.addListener( getModel() ); + + mSampleCount = (int)( 2000 * tap.getSampleRateRatio() ); + + getModel().setSampleCount( mSampleCount ); + getModel().setDelay( tap.getDelay() ); + + Log.info( "Float Tap Panel [" + tap.getName() + "] count: " + + getModel().getSampleCount() + " delay:" + getModel().getDelay() ); + } + + @Override + public void update( Observable arg0, Object arg1 ) + { + mSamples = (List)getModel().getSamples(); + repaint(); + } + + public void paintComponent( Graphics g ) + { + super.paintComponent( g ); + + Graphics2D g2 = (Graphics2D)g; + + int height = getHeight(); + int width = getWidth(); + + g2.setColor( Color.DARK_GRAY ); + + float middle = (float)height / 2.0f; + + g2.drawLine( 0, (int)middle, width, (int)middle ); + + if( mSamples != null && + mSamples.size() > 0 ) + { + g2.setColor( getForeground() ); + + g2.drawString( mLabel, 5 , 15 ); + + Path2D.Float polyline = + new Path2D.Float( Path2D.Float.WIND_EVEN_ODD, mSamples.size() ); + + /* Move to the first point */ + Point2D.Float first = resolve( 0, mSamples.get( 0 ) ); + polyline.moveTo( first.x, first.y ); + + for( int x = 1; x < mSamples.size(); x++ ) + { + Point2D.Float point = resolve( x, mSamples.get( x ) ); + + polyline.lineTo( point.x, point.y ); + } + + g2.draw(polyline); + } + + g2.dispose(); + } + + private Point2D.Float resolve( int index, float value ) + { + int middle = (int)( getHeight() / 2 ); + + double scaledValue = Math.asin( value ); + + float y = middle - (float)( middle * scaledValue * mVerticalZoom ); + + /* Clip y to zero as necessary */ + y = ( y < 0 ) ? 0 : y; + + /* Clip y to max height as necessary */ + y = ( y > getHeight() ) ? getHeight() : y; + + float indexWidth = (float)getWidth() / (float)mSampleCount; + + float x = (float)index * indexWidth; + + Point2D.Float point = new Point2D.Float( x, y ); + + return new Point2D.Float( x , y ); + } +} diff --git a/src/instrument/gui/FloatTapViewPanel.java b/src/instrument/tap/stream/FloatTapViewPanel.java similarity index 95% rename from src/instrument/gui/FloatTapViewPanel.java rename to src/instrument/tap/stream/FloatTapViewPanel.java index d2f48b48..bfc244f6 100644 --- a/src/instrument/gui/FloatTapViewPanel.java +++ b/src/instrument/tap/stream/FloatTapViewPanel.java @@ -15,10 +15,11 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see ******************************************************************************/ -package instrument.gui; +package instrument.tap.stream; +import instrument.gui.SampleModel; import instrument.tap.Tap; -import instrument.tap.stream.FloatTap; +import instrument.tap.TapViewPanel; import java.awt.Color; import java.awt.Graphics; @@ -108,6 +109,7 @@ public class FloatTapViewPanel extends TapViewPanel float scaledValue = value / (float)Short.MAX_VALUE; + Log.info( "Float value is:" + scaledValue ); float y = middle - ( middle * scaledValue * mVerticalZoom ); /* Clip y to zero as necessary */ diff --git a/src/instrument/gui/SymbolEventTapViewPanel.java b/src/instrument/tap/stream/SymbolEventTapViewPanel.java similarity index 98% rename from src/instrument/gui/SymbolEventTapViewPanel.java rename to src/instrument/tap/stream/SymbolEventTapViewPanel.java index a980b4a0..f4ec914f 100644 --- a/src/instrument/gui/SymbolEventTapViewPanel.java +++ b/src/instrument/tap/stream/SymbolEventTapViewPanel.java @@ -15,9 +15,10 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see ******************************************************************************/ -package instrument.gui; +package instrument.tap.stream; -import instrument.tap.stream.SymbolEventTap; +import instrument.gui.SampleModel; +import instrument.tap.TapViewPanel; import java.awt.Color; import java.awt.FontMetrics; diff --git a/src/log/Log.java b/src/log/Log.java index 058b09fc..346cdfc4 100644 --- a/src/log/Log.java +++ b/src/log/Log.java @@ -86,6 +86,32 @@ public class Log System.out.println( ( label == null ? "" : label + ": " ) + msg ); } + private static void log( String msg, String label, Throwable throwable ) + { + StringBuilder sb = new StringBuilder(); + + if( msg != null ) + { + sb.append( msg ); + sb.append( "\n" ); + } + + sb.append( "---- STACK TRACE ----\n" ); + sb.append( "\tERROR: " ); + sb.append( throwable.getLocalizedMessage() + "\n" ); + + StackTraceElement[] stack = throwable.getStackTrace(); + + for( StackTraceElement element: stack ) + { + sb.append( "\t" ); + sb.append( element.toString() ); + sb.append( "\n" ); + } + + log( sb.toString(), label ); + } + public static void header( String name ) { StringBuilder sb = new StringBuilder(); @@ -138,11 +164,21 @@ public class Log log( msg, "ERROR" ); } + public static void error( String msg, Throwable throwable ) + { + log( msg, "ERROR", throwable ); + } + public static void debug( String msg ) { log( msg, "DEBUG" ); } + public static void debug( String msg, Throwable throwable ) + { + log( msg, "DEBUG", throwable ); + } + /** * Initializes logging to the specified filename */ diff --git a/src/settings/Settings.java b/src/settings/Settings.java index f799660a..3144bbc7 100644 --- a/src/settings/Settings.java +++ b/src/settings/Settings.java @@ -26,10 +26,8 @@ import javax.xml.bind.annotation.XmlSeeAlso; import map.DefaultIcon; import map.MapIcon; - -import org.jdesktop.swingx.mapviewer.GeoPosition; - import settings.ColorSetting.ColorSettingName; +import source.recording.RecordingConfiguration; import source.tuner.TunerConfiguration; import source.tuner.TunerConfigurationAssignment; import source.tuner.TunerType; @@ -50,6 +48,7 @@ import source.tuner.rtl.r820t.R820TTunerConfiguration; MapViewSetting.class, MapIcon.class, R820TTunerConfiguration.class, + RecordingConfiguration.class, Setting.class, TunerConfiguration.class, TunerConfigurationAssignment.class} ) @@ -65,10 +64,35 @@ public class Settings private ArrayList mConfigurationAssignments = new ArrayList(); + private ArrayList mRecordingConfigurations = + new ArrayList(); + public Settings() { } + @XmlElement( name="recording_configuration" ) + public ArrayList getRecordingConfigurations() + { + return mRecordingConfigurations; + } + + public void setRecordingConfigurations( ArrayList configs ) + { + mRecordingConfigurations = configs; + } + + public void addRecordingConfiguration( RecordingConfiguration config ) + { + mRecordingConfigurations.add( config ); + + } + + public void removeRecordingConfiguration( RecordingConfiguration config ) + { + mRecordingConfigurations.remove( config ); + } + @XmlElement( name="tuner_configuration" ) public ArrayList getTunerConfigurations() { diff --git a/src/settings/SettingsManager.java b/src/settings/SettingsManager.java index 83066d2c..0fe0f0b5 100644 --- a/src/settings/SettingsManager.java +++ b/src/settings/SettingsManager.java @@ -45,6 +45,7 @@ import org.jdesktop.swingx.mapviewer.GeoPosition; import properties.SystemProperties; import settings.ColorSetting.ColorSettingName; +import source.recording.RecordingConfiguration; import source.tuner.TunerConfiguration; import source.tuner.TunerConfigurationAssignment; import source.tuner.TunerType; @@ -517,6 +518,23 @@ public class SettingsManager broadcastSettingChange( setting ); } + public ArrayList getRecordingConfigurations() + { + return mSettings.getRecordingConfigurations(); + } + + public void addRecordingConfiguration( RecordingConfiguration config ) + { + mSettings.addRecordingConfiguration( config ); + save(); + } + + public void removeRecordingConfiguration( RecordingConfiguration config ) + { + mSettings.removeRecordingConfiguration( config ); + save(); + } + public ArrayList getTunerConfigurations( TunerType type ) { ArrayList configs = getSettings() diff --git a/src/source/EmptySourceEditor.java b/src/source/EmptySourceEditor.java index 4f711ca2..ad1c1669 100644 --- a/src/source/EmptySourceEditor.java +++ b/src/source/EmptySourceEditor.java @@ -20,14 +20,16 @@ package source; import javax.swing.JLabel; import source.config.SourceConfiguration; +import controller.ResourceManager; public class EmptySourceEditor extends SourceEditor { private static final long serialVersionUID = 1L; - public EmptySourceEditor( SourceConfiguration config ) + public EmptySourceEditor( ResourceManager resourceManager, + SourceConfiguration config ) { - super( config ); + super( resourceManager, config ); initGUI(); } diff --git a/src/source/SourceComponentEditor.java b/src/source/SourceComponentEditor.java index bb5c0606..3e879efe 100644 --- a/src/source/SourceComponentEditor.java +++ b/src/source/SourceComponentEditor.java @@ -73,7 +73,8 @@ public class SourceComponentEditor extends AbstractChannelEditor } //Change to the new one - mEditor = SourceEditorFactory.getPanel( config ); + mEditor = SourceEditorFactory.getPanel( + mChannelNode.getModel().getResourceManager(), config ); //Add it to the jpanel add( mEditor, "span 2" ); diff --git a/src/source/SourceEditor.java b/src/source/SourceEditor.java index 2d9ea0b3..dd5ad11e 100644 --- a/src/source/SourceEditor.java +++ b/src/source/SourceEditor.java @@ -19,15 +19,18 @@ package source; import source.config.SourceConfiguration; import controller.Editor; +import controller.ResourceManager; public abstract class SourceEditor extends Editor { private static final long serialVersionUID = 1L; + protected ResourceManager mResourceManager; protected SourceConfiguration mConfig; - public SourceEditor( SourceConfiguration config ) + public SourceEditor( ResourceManager resourceManager, SourceConfiguration config ) { + mResourceManager = resourceManager; mConfig = config; } @@ -35,4 +38,9 @@ public abstract class SourceEditor extends Editor { return mConfig; } + + public ResourceManager getResourceManager() + { + return mResourceManager; + } } diff --git a/src/source/SourceEditorFactory.java b/src/source/SourceEditorFactory.java index af641318..b918f79f 100644 --- a/src/source/SourceEditorFactory.java +++ b/src/source/SourceEditorFactory.java @@ -19,25 +19,31 @@ package source; import source.config.SourceConfiguration; import source.mixer.MixerEditor; +import source.recording.RecordingEditor; import source.tuner.TunerEditor; +import controller.ResourceManager; public class SourceEditorFactory { - public static SourceEditor getPanel( SourceConfiguration config ) + public static SourceEditor getPanel( ResourceManager resourceManager, + SourceConfiguration config ) { SourceEditor configuredPanel; switch( config.getSourceType() ) { case MIXER: - configuredPanel = new MixerEditor( config ); + configuredPanel = new MixerEditor( resourceManager, config ); break; case TUNER: - configuredPanel = new TunerEditor( config ); + configuredPanel = new TunerEditor( resourceManager, config ); + break; + case RECORDING: + configuredPanel = new RecordingEditor( resourceManager, config ); break; case NONE: default: - configuredPanel = new EmptySourceEditor( config ); + configuredPanel = new EmptySourceEditor( resourceManager, config ); break; } diff --git a/src/source/SourceManager.java b/src/source/SourceManager.java index a518ae2a..7febb994 100644 --- a/src/source/SourceManager.java +++ b/src/source/SourceManager.java @@ -40,7 +40,7 @@ public class SourceManager { mResourceManager = resourceManager; } - public Source getSource( ProcessingChain channel ) + public Source getSource( ProcessingChain channel ) throws SourceException { Source retVal = null; @@ -55,6 +55,9 @@ public class SourceManager { case TUNER: retVal = mResourceManager.getTunerManager().getSource( channel ); break; + case RECORDING: + retVal = mResourceManager.getRecordingSourceManager() + .getSource( channel ); case NONE: default: break; diff --git a/src/source/SourceType.java b/src/source/SourceType.java index d8947f7d..be04d87c 100644 --- a/src/source/SourceType.java +++ b/src/source/SourceType.java @@ -21,8 +21,8 @@ public enum SourceType { NONE( "No Source" ), MIXER( "Mixer/Sound Card" ), - TUNER( "Tuner" ); -// WAVE( "Wave File" ); + TUNER( "Tuner" ), + RECORDING( "IQ Recording" ); private String mDisplayString; diff --git a/src/source/config/SourceConfigFactory.java b/src/source/config/SourceConfigFactory.java index 9784548e..2cedeaec 100644 --- a/src/source/config/SourceConfigFactory.java +++ b/src/source/config/SourceConfigFactory.java @@ -27,7 +27,7 @@ public class SourceConfigFactory } public static SourceConfiguration - getSourceConfiguration( SourceType source ) + getSourceConfiguration( SourceType source ) { SourceConfiguration retVal; @@ -39,6 +39,9 @@ public class SourceConfigFactory case TUNER: retVal = new SourceConfigTuner(); break; + case RECORDING: + retVal = new SourceConfigRecording(); + break; case NONE: default: retVal = new SourceConfigNone(); diff --git a/src/source/config/SourceConfigRecording.java b/src/source/config/SourceConfigRecording.java new file mode 100644 index 00000000..43204a2d --- /dev/null +++ b/src/source/config/SourceConfigRecording.java @@ -0,0 +1,61 @@ +/******************************************************************************* + * SDR Trunk + * Copyright (C) 2014 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + ******************************************************************************/ +package source.config; + +import javax.xml.bind.annotation.XmlAttribute; + +import source.SourceType; + +public class SourceConfigRecording extends SourceConfiguration +{ + private String mRecordingAlias; + private long mFrequency; + + public SourceConfigRecording() + { + super( SourceType.RECORDING ); + } + + @XmlAttribute( name = "recording_alias" ) + public String getRecordingAlias() + { + return mRecordingAlias; + } + + public void setRecordingAlias( String alias ) + { + mRecordingAlias = alias; + } + + @XmlAttribute( name = "frequency" ) + public long getFrequency() + { + return mFrequency; + } + + public void setFrequency( long frequency ) + { + mFrequency = frequency; + } + + @Override + public String getDescription() + { + return getRecordingAlias(); + } +} diff --git a/src/source/mixer/MixerEditor.java b/src/source/mixer/MixerEditor.java index 7c23c2f2..fb10f77b 100644 --- a/src/source/mixer/MixerEditor.java +++ b/src/source/mixer/MixerEditor.java @@ -27,6 +27,7 @@ import javax.swing.JLabel; import source.SourceEditor; import source.config.SourceConfigMixer; import source.config.SourceConfiguration; +import controller.ResourceManager; public class MixerEditor extends SourceEditor { @@ -36,9 +37,10 @@ public class MixerEditor extends SourceEditor protected Mixer.Info mSelectedMixer = null; - public MixerEditor( SourceConfiguration config ) + public MixerEditor( ResourceManager resourceManager, + SourceConfiguration config ) { - super( config ); + super( resourceManager, config ); initGUI(); } diff --git a/src/source/recording/Recording.java b/src/source/recording/Recording.java new file mode 100644 index 00000000..d19de698 --- /dev/null +++ b/src/source/recording/Recording.java @@ -0,0 +1,108 @@ +package source.recording; + +import java.io.File; +import java.util.ArrayList; +import java.util.concurrent.RejectedExecutionException; + +import source.SourceException; +import source.tuner.FrequencyChangeListener; +import source.tuner.TunerChannel; +import source.tuner.TunerChannelProvider; +import source.tuner.TunerChannelSource; +import controller.ResourceManager; +import controller.ThreadPoolManager; + +public class Recording implements Comparable, + FrequencyChangeListener, + TunerChannelProvider +{ + private ResourceManager mResourceManager; + private RecordingConfiguration mConfiguration; + + private ArrayList mTunerChannels = + new ArrayList(); + + private long mCenterFrequency; + + public Recording( ResourceManager resourceManager, + RecordingConfiguration configuration ) + { + mResourceManager = resourceManager; + mConfiguration = configuration; + mCenterFrequency = mConfiguration.getCenterFrequency(); + } + + public void setAlias( String alias ) + { + mConfiguration.setAlias( alias ); + mResourceManager.getSettingsManager().save(); + } + + public void setRecordingFile( File file ) throws SourceException + { + if( hasChannels() ) + { + throw new SourceException( "Recording source - can't change " + + "recording file while channels are enabled against the " + + "current recording." ); + } + + mConfiguration.setFilePath( file.getAbsolutePath() ); + mResourceManager.getSettingsManager().save(); + } + + /** + * Indicates if this recording is currently playing/providing samples to + * tuner channel sources + */ + public boolean hasChannels() + { + return mTunerChannels.size() > 0; + } + + @Override + public TunerChannelSource getChannel( ThreadPoolManager threadPoolManager, + TunerChannel channel ) throws RejectedExecutionException, + SourceException + { + // TODO Auto-generated method stub + return null; + } + + @Override + public void releaseChannel( TunerChannelSource source ) + { + // TODO Auto-generated method stub + } + + public RecordingConfiguration getRecordingConfiguration() + { + return mConfiguration; + } + + public String toString() + { + return mConfiguration.getAlias(); + } + + @Override + public void frequencyChanged( long frequency, int bandwidth ) + { + mConfiguration.setCenterFrequency( frequency ); + mResourceManager.getSettingsManager().save(); + + mCenterFrequency = frequency; + + for( TunerChannelSource channel: mTunerChannels ) + { + channel.frequencyChanged( frequency, bandwidth ); + } + } + + @Override + public int compareTo( Recording other ) + { + return getRecordingConfiguration().getAlias() + .compareTo( other.getRecordingConfiguration().getAlias() ); + } +} diff --git a/src/source/recording/RecordingConfiguration.java b/src/source/recording/RecordingConfiguration.java new file mode 100644 index 00000000..25fd9333 --- /dev/null +++ b/src/source/recording/RecordingConfiguration.java @@ -0,0 +1,87 @@ +package source.recording; + +import java.io.File; + +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlTransient; + +public class RecordingConfiguration +{ + private File mRecording; + private String mFilePath; + private String mAlias; + private long mCenterFrequency; + + public RecordingConfiguration() + { + mAlias = "New Recording"; + } + + public RecordingConfiguration( String filePath, + String alias, + long centerFrequency ) + { + mFilePath = filePath; + mAlias = alias; + mCenterFrequency = centerFrequency; + + updateFile(); + } + + private void updateFile() + { + if( mFilePath != null ) + { + try + { + mRecording = new File( mFilePath ); + } + catch( Exception e ) + { + throw new IllegalArgumentException( "There was an error while " + + "accessing the recording source [" + mFilePath + "]", e ); + } + } + } + + @XmlTransient + public File getRecording() + { + return mRecording; + } + + @XmlAttribute( name = "file_path" ) + public String getFilePath() + { + return mFilePath; + } + + public void setFilePath( String filePath ) + { + mFilePath = filePath; + + updateFile(); + } + + @XmlAttribute( name = "alias" ) + public String getAlias() + { + return mAlias; + } + + public void setAlias( String alias ) + { + mAlias = alias; + } + + @XmlAttribute( name = "center_frequency" ) + public long getCenterFrequency() + { + return mCenterFrequency; + } + + public void setCenterFrequency( long frequency ) + { + mCenterFrequency = frequency; + } +} diff --git a/src/source/recording/RecordingEditor.java b/src/source/recording/RecordingEditor.java new file mode 100644 index 00000000..aabec88f --- /dev/null +++ b/src/source/recording/RecordingEditor.java @@ -0,0 +1,120 @@ +/******************************************************************************* + * SDR Trunk + * Copyright (C) 2014 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + ******************************************************************************/ +package source.recording; + +import gui.control.JFrequencyControl; + +import java.util.List; + +import javax.swing.DefaultComboBoxModel; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.SwingUtilities; + +import source.SourceEditor; +import source.config.SourceConfigRecording; +import source.config.SourceConfigTuner; +import source.config.SourceConfiguration; +import controller.ResourceManager; + +public class RecordingEditor extends SourceEditor +{ + private static final long serialVersionUID = 1L; + private JComboBox mComboRecordings; + private JFrequencyControl mFrequencyControl; + + public RecordingEditor( ResourceManager resourceManager, + SourceConfiguration config ) + { + super( resourceManager, config ); + + initGUI(); + } + + public void reset() + { + mFrequencyControl.setFrequency( + ((SourceConfigTuner)mConfig).getFrequency(), false ); + } + + public void save() + { + SourceConfigRecording config = (SourceConfigRecording)mConfig; + + Recording selected = (Recording)mComboRecordings.getSelectedItem(); + + if( selected != null ) + { + config.setRecordingAlias( selected + .getRecordingConfiguration().getAlias() ); + } + + config.setFrequency( mFrequencyControl.getFrequency() ); + } + + private void initGUI() + { + add( new JLabel( "Recording" ) ); + + mComboRecordings = new JComboBox(); + + add( mComboRecordings, "wrap" ); + + mFrequencyControl = new JFrequencyControl(); + + mFrequencyControl.setFrequency( + ((SourceConfigRecording)mConfig).getFrequency(), false ); + + add( mFrequencyControl, "span" ); + + resetRecordings(); + } + + private void resetRecordings() + { + SwingUtilities.invokeLater( new Runnable() + { + @Override + public void run() + { + List recordings = getResourceManager() + .getRecordingSourceManager().getRecordings(); + + mComboRecordings.setModel( + new DefaultComboBoxModel( + recordings.toArray( new Recording[ recordings.size()] ) ) ); + + SourceConfigRecording config = (SourceConfigRecording)mConfig; + + String recordingAlias = config.getRecordingAlias(); + + if( recordingAlias != null ) + { + Recording selected = mResourceManager + .getRecordingSourceManager() + .getRecordingFromAlias( recordingAlias ); + + if( selected != null ) + { + mComboRecordings.setSelectedItem( selected ); + } + } + } + }); + } +} diff --git a/src/source/recording/RecordingEditorPanel.java b/src/source/recording/RecordingEditorPanel.java new file mode 100644 index 00000000..03c3cb2b --- /dev/null +++ b/src/source/recording/RecordingEditorPanel.java @@ -0,0 +1,131 @@ +package source.recording; + +import gui.control.JFrequencyControl; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.io.File; + +import javax.swing.JButton; +import javax.swing.JFileChooser; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import source.tuner.FrequencyChangeListener; +import net.miginfocom.swing.MigLayout; + +public class RecordingEditorPanel extends JPanel +{ + private static final long serialVersionUID = 1L; + + private RecordingNode mRecordingNode; + + private JTextField mRecordingAlias; + private JLabel mFilePathLabel; + + public RecordingEditorPanel( RecordingNode recordingNode ) + { + mRecordingNode = recordingNode; + + initGUI(); + } + + private void initGUI() + { + setLayout( new MigLayout( "fill,wrap 2", "[right,grow][grow]", "[][][][][][grow]" ) ); + + add( new JLabel( "I/Q Recording" ), "span,align left" ); + + add( new JLabel( "Alias:" ) ); + + mRecordingAlias = new JTextField( mRecordingNode.getRecording() + .getRecordingConfiguration().getAlias() ); + + mRecordingAlias.addFocusListener( new FocusListener() + { + @Override + public void focusLost( FocusEvent arg0 ) + { + String alias = mRecordingAlias.getText(); + + if( alias != null ) + { + mRecordingNode.getRecording().setAlias( alias ); + } + } + + @Override + public void focusGained( FocusEvent arg0 ) {} + } ); + + add( mRecordingAlias, "wrap" ); + + JButton mFileSelectionButton = new JButton( "File ..." ); + mFileSelectionButton.addActionListener( new ActionListener() + { + @Override + public void actionPerformed( ActionEvent arg0 ) + { + if( mRecordingNode.getRecording().hasChannels() ) + { + JOptionPane.showMessageDialog( RecordingEditorPanel.this, + "This recording is currently in replay. Please " + + "disable any channels that are using this " + + "recording before changing the recording file." ); + } + else + { + final JFileChooser fc = new JFileChooser(); + + int returnVal = fc.showOpenDialog( RecordingEditorPanel.this ); + + if ( returnVal == JFileChooser.APPROVE_OPTION ) + { + File file = fc.getSelectedFile(); + + if( file != null && file.exists() ) + { + mRecordingNode.getRecording() + .getRecordingConfiguration() + .setFilePath( file.getAbsolutePath() ); + + if( mFilePathLabel != null ) + { + mFilePathLabel.setText( file.getName() ); + } + } + } + } + } + } ); + add( mFileSelectionButton ); + + String filePath = mRecordingNode.getRecording() + .getRecordingConfiguration().getFilePath(); + + if( filePath != null ) + { + mFilePathLabel = new JLabel( filePath ); + } + else + { + mFilePathLabel = new JLabel( "Please select a recording" ); + } + + add( mFilePathLabel, "wrap" ); + + add( new JLabel( "Center Frequency:" ), "span,align left" ); + + JFrequencyControl frequencyControl = new JFrequencyControl( + mRecordingNode.getRecording().getRecordingConfiguration() + .getCenterFrequency() ); + + frequencyControl.addListener( mRecordingNode.getRecording() ); + + add( frequencyControl, "span,align left" ); + } +} diff --git a/src/source/recording/RecordingGroupNode.java b/src/source/recording/RecordingGroupNode.java new file mode 100644 index 00000000..8eb2029a --- /dev/null +++ b/src/source/recording/RecordingGroupNode.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * SDR Trunk + * Copyright (C) 2014 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + ******************************************************************************/ +package source.recording; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.List; + +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; + +import controller.BaseNode; + +public class RecordingGroupNode extends BaseNode +{ + private static final long serialVersionUID = 1L; + + public RecordingGroupNode() + { + super( null ); + } + + public String toString() + { + return "Recordings [" + this.getChildCount() + "]"; + } + + public JPopupMenu getContextMenu() + { + JPopupMenu menu = new JPopupMenu(); + + JMenuItem addRecordingItem = new JMenuItem( "Add Recording" ); + + addRecordingItem.addActionListener( new ActionListener() + { + @Override + public void actionPerformed( ActionEvent e ) + { + RecordingConfiguration config = new RecordingConfiguration(); + + getModel().getResourceManager().getSettingsManager() + .addRecordingConfiguration( config ); + + Recording recording = getModel().getResourceManager() + .getRecordingSourceManager().addRecording( config ); + + RecordingNode node = new RecordingNode( recording ); + + getModel().addNode( node, RecordingGroupNode.this, 0 ); + + sort(); + + node.show(); + } + } ); + + menu.add( addRecordingItem ); + + return menu; + } + + public void loadRecordings() + { + List recordings = getModel().getResourceManager() + .getRecordingSourceManager().getRecordings(); + + for( Recording recording: recordings ) + { + RecordingNode node = new RecordingNode( recording ); + + getModel().addNode( node, this, 0 ); + } + + sort(); + } +} diff --git a/src/source/recording/RecordingNode.java b/src/source/recording/RecordingNode.java new file mode 100644 index 00000000..54ec12fd --- /dev/null +++ b/src/source/recording/RecordingNode.java @@ -0,0 +1,79 @@ +package source.recording; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPopupMenu; + +import controller.BaseNode; + +public class RecordingNode extends BaseNode +{ + private static final long serialVersionUID = 1L; + + private JPanel mEditor; + + public RecordingNode( Recording recording ) + { + super( recording ); + } + + public Recording getRecording() + { + return (Recording)getUserObject(); + } + + public String toString() + { + return getRecording().toString(); + } + + @Override + public JPanel getEditor() + { + if( mEditor == null ) + { + mEditor = new RecordingEditorPanel( this ); + } + + return mEditor; + } + + public JPopupMenu getContextMenu() + { + JPopupMenu menu = new JPopupMenu(); + + JMenuItem deleteItem = new JMenuItem( "Delete" ); + + deleteItem.addActionListener( new ActionListener() + { + @Override + public void actionPerformed( ActionEvent e ) + { + if( getRecording().hasChannels() ) + { + JOptionPane.showMessageDialog( getModel().getTree(), + "Can't delete recording - currently in use. Please" + + " disable any channel(s) that are using this " + + "recording before deleting it", + "Can't delete recording", + JOptionPane.ERROR_MESSAGE ); + } + else + { + getModel().getResourceManager().getRecordingSourceManager() + .removeRecording( getRecording() ); + + getModel().removeNodeFromParent( RecordingNode.this ); + } + } + } ); + + menu.add( deleteItem ); + + return menu; + } +} diff --git a/src/source/recording/RecordingSourceManager.java b/src/source/recording/RecordingSourceManager.java new file mode 100644 index 00000000..2db8d8b2 --- /dev/null +++ b/src/source/recording/RecordingSourceManager.java @@ -0,0 +1,152 @@ +/******************************************************************************* + * SDR Trunk + * Copyright (C) 2014 Dennis Sheirer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + ******************************************************************************/ +package source.recording; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import log.Log; +import settings.SettingsManager; +import source.Source; +import source.SourceException; +import source.config.SourceConfigRecording; +import source.tuner.TunerChannel; +import source.tuner.TunerChannelSource; +import controller.ResourceManager; +import controller.channel.ProcessingChain; + +public class RecordingSourceManager +{ + private ResourceManager mResourceManager; + + private ArrayList mRecordings = new ArrayList(); + + public RecordingSourceManager( ResourceManager resourceManager ) + { + mResourceManager = resourceManager; + + loadRecordings(); + } + + /** + * Iterates current recordings to get a tuner channel source for the frequency + * specified in the channel config's source config object + */ + public Source getSource( ProcessingChain processingChain ) + throws SourceException + { + TunerChannelSource retVal = null; + + if( processingChain.getChannel().getSourceConfiguration() + instanceof SourceConfigRecording ) + { + TunerChannel tunerChannel = + processingChain.getChannel().getTunerChannel(); + + SourceConfigRecording config = (SourceConfigRecording)processingChain + .getChannel().getSourceConfiguration(); + + Recording recording = getRecordingFromAlias( config.getRecordingAlias() ); + + if( recording != null ) + { + retVal = recording.getChannel( + mResourceManager.getThreadPoolManager(), + tunerChannel ); + } + else + { + throw new SourceException( "Recording with alias name [" + + config.getRecordingAlias() + "] is not currently available" ); + } + } + + return retVal; + } + + public Recording getRecordingFromAlias( String alias ) + { + for( Recording recording: mRecordings ) + { + if( recording.getRecordingConfiguration() + .getAlias().contentEquals( alias ) ) + { + return recording; + } + } + + return null; + } + + /** + * Get list of loaded IQ files + */ + public List getRecordings() + { + Collections.sort( mRecordings ); + + return mRecordings; + } + + public void addRecording( Recording recording ) + { + mRecordings.add( recording ); + } + + public Recording addRecording( RecordingConfiguration config ) + { + Recording recording = new Recording( mResourceManager, config ); + + mRecordings.add( recording ); + + return recording; + } + + public void removeRecording( Recording recording ) + { + /* Remove the config from the settings manager */ + mResourceManager.getSettingsManager().removeRecordingConfiguration( + recording.getRecordingConfiguration() ); + + mRecordings.remove( recording ); + } + + private void loadRecordings() + { + if( mResourceManager != null ) + { + SettingsManager settingsManager = mResourceManager.getSettingsManager(); + + if( settingsManager != null ) + { + ArrayList recordingConfigurations = + settingsManager.getRecordingConfigurations(); + + Log.info( "RecordingSourceManager - discovered [" + recordingConfigurations.size() + "] recording configurations" ); + + for( RecordingConfiguration config: recordingConfigurations ) + { + Recording recording = new Recording( mResourceManager, config ); + + mRecordings.add( recording ); + } + } + } + } +} diff --git a/src/source/tuner/Tuner.java b/src/source/tuner/Tuner.java index 7c7ca34e..f4df21ae 100644 --- a/src/source/tuner/Tuner.java +++ b/src/source/tuner/Tuner.java @@ -31,7 +31,8 @@ import controller.ThreadPoolManager; * Tuner - provides tuner channel sources, representing a channel frequency */ public abstract class Tuner implements FrequencyChangeBroadcaster, - FrequencyChangeListener + FrequencyChangeListener, + TunerChannelProvider { private String mName; diff --git a/src/source/tuner/TunerChannelProvider.java b/src/source/tuner/TunerChannelProvider.java new file mode 100644 index 00000000..f8fabe48 --- /dev/null +++ b/src/source/tuner/TunerChannelProvider.java @@ -0,0 +1,28 @@ +package source.tuner; + +import java.util.concurrent.RejectedExecutionException; + +import source.SourceException; +import controller.ThreadPoolManager; + +public interface TunerChannelProvider +{ + /** + * Returns a tuner frequency channel source, tuned to the correct frequency + * + * @param frequency - desired frequency + * + * @return - source for 48k sample rate + */ + public abstract TunerChannelSource getChannel( ThreadPoolManager threadPoolManager, + TunerChannel channel ) throws RejectedExecutionException, SourceException; + + /** + * Releases the tuned channel resources + * + * @param channel - previously obtained tuner channel + */ + public abstract void releaseChannel( TunerChannelSource source ); + + +} diff --git a/src/source/tuner/TunerEditor.java b/src/source/tuner/TunerEditor.java index ab51b52b..15b3d650 100644 --- a/src/source/tuner/TunerEditor.java +++ b/src/source/tuner/TunerEditor.java @@ -21,15 +21,17 @@ import gui.control.JFrequencyControl; import source.SourceEditor; import source.config.SourceConfigTuner; import source.config.SourceConfiguration; +import controller.ResourceManager; public class TunerEditor extends SourceEditor { private static final long serialVersionUID = 1L; private JFrequencyControl mFrequencyControl; - public TunerEditor( SourceConfiguration config ) + public TunerEditor( ResourceManager resourceManager, + SourceConfiguration config ) { - super( config ); + super( resourceManager, config ); initGUI(); } diff --git a/src/source/tuner/TunerManager.java b/src/source/tuner/TunerManager.java index 55ff7f21..b4c4a064 100644 --- a/src/source/tuner/TunerManager.java +++ b/src/source/tuner/TunerManager.java @@ -65,15 +65,15 @@ public class TunerManager * Iterates current tuners to get a tuner channel source for the frequency * specified in the channel config's source config object */ - public Source getSource( ProcessingChain channel ) + public Source getSource( ProcessingChain processingChain ) { TunerChannelSource retVal = null; - if( channel.getChannel().getSourceConfiguration() + if( processingChain.getChannel().getSourceConfiguration() instanceof SourceConfigTuner ) { - TunerChannel tunerChannel = channel.getChannel().getTunerChannel(); + TunerChannel tunerChannel = processingChain.getChannel().getTunerChannel(); Iterator it = mTuners.iterator(); diff --git a/src/source/tuner/rtl/RTL2832TunerController.java b/src/source/tuner/rtl/RTL2832TunerController.java index 3e525847..c10c49ae 100644 --- a/src/source/tuner/rtl/RTL2832TunerController.java +++ b/src/source/tuner/rtl/RTL2832TunerController.java @@ -368,16 +368,24 @@ public abstract class RTL2832TunerController extends TunerController if( claim( iface ) ) { + Log.debug( "RTL2832 - usb interface claimed" ); + /* Perform a dummy write to see if the device needs reset */ writeRegister( device, Block.USB, Address.USB_SYSCTL.getAddress(), 0x09, 1 ); + Log.debug( "RTL2832 - initializing baseband" ); + /* Initialize the baseband */ initBaseband( device ); + Log.debug( "RTL2832 - enabling I2C repeater" ); + enableI2CRepeater( device, true ); boolean controlI2CRepeater = false; + Log.debug( "RTL2832 - testing for tuner types" ); + /* Test for each tuner type until we find the correct one */ if( isE4000Tuner( device, controlI2CRepeater ) ) { @@ -404,15 +412,19 @@ public abstract class RTL2832TunerController extends TunerController tunerClass = TunerType.FITIPOWER_FC0012; } + Log.debug( "RTL2832 - disabling I2C repeater" ); + enableI2CRepeater( device, false ); + Log.debug( "RTL2832 - releasing USB interface" ); + iface.release(); } } catch( Exception e ) { Log.error( "RTL2832TunerController - error while determining " - + "tuner type - " + e.getLocalizedMessage() ); + + "tuner type - ", e ); } return tunerClass; @@ -927,6 +939,8 @@ public abstract class RTL2832TunerController extends TunerController protected static boolean isE4000Tuner( UsbDevice device, boolean controlI2CRepeater ) throws UsbClaimException { + Log.debug( "RTL2832 - testing for E4000 tuner" ); + try { int value = readI2CRegister( device, E4K_I2C_ADDRESS, @@ -974,6 +988,7 @@ public abstract class RTL2832TunerController extends TunerController boolean controlI2CRepeater ) throws UsbClaimException { + Log.debug( "RTL2832 - testing for F0013 tuner" ); try { int value = readI2CRegister( device, FC0013_I2C_ADDRESS, @@ -1030,6 +1045,7 @@ public abstract class RTL2832TunerController extends TunerController protected static boolean isR820TTuner( UsbDevice device, boolean controlI2CRepeater ) throws UsbClaimException { + Log.debug( "RTL2832 - testing for R820T tuner" ); try { int value = readI2CRegister( device, R820T_I2C_ADDRESS,