diff --git a/src/main/java/io/github/dsheirer/source/mixer/MixerManager.java b/src/main/java/io/github/dsheirer/source/mixer/MixerManager.java index d674afc3a..a68012e7c 100644 --- a/src/main/java/io/github/dsheirer/source/mixer/MixerManager.java +++ b/src/main/java/io/github/dsheirer/source/mixer/MixerManager.java @@ -20,7 +20,6 @@ import io.github.dsheirer.sample.adapter.RealShortAdapter; import io.github.dsheirer.source.config.SourceConfigMixer; import io.github.dsheirer.source.config.SourceConfiguration; -import io.github.dsheirer.source.tuner.MixerTunerDataLine; import io.github.dsheirer.source.tuner.MixerTunerType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,7 +34,6 @@ import javax.sound.sampled.SourceDataLine; import javax.sound.sampled.TargetDataLine; import java.util.ArrayList; -import java.util.Collection; import java.util.EnumSet; import java.util.List; @@ -46,7 +44,10 @@ public class MixerManager { private final static Logger mLog = LoggerFactory.getLogger(MixerManager.class); - public MixerManager() {} + public MixerManager() + { + + } public static RealMixerSource getSource(SourceConfiguration config) { @@ -193,33 +194,69 @@ public static List getOutputMixers() return outputMixers; } - public static Collection getMixerTunerDataLines() + /** + * Obtains a target data line for the specified mixer tuner type + * @param mixerTunerType to find + * @return target data line or null. + */ + public static TargetDataLine getTunerTargetDataLine(MixerTunerType mixerTunerType) { - List tuners = new ArrayList<>(); - for(Mixer.Info mixerInfo : AudioSystem.getMixerInfo()) { - //Sort between the mixers and the tuner mixers, and load each - MixerTunerType mixerTunerType = MixerTunerType.getMixerTunerType(mixerInfo); + MixerTunerType type = MixerTunerType.getMixerTunerType(mixerInfo); - if(mixerTunerType != MixerTunerType.UNKNOWN) + if(type != null && type == mixerTunerType) { - TargetDataLine tdl = getTargetDataLine(mixerInfo, mixerTunerType.getAudioFormat()); + Mixer mixer = AudioSystem.getMixer(mixerInfo); - if(tdl != null) + if(mixer != null) { - switch(mixerTunerType) + for(Line.Info info : mixer.getTargetLineInfo()) { - case FUNCUBE_DONGLE_PRO: - case FUNCUBE_DONGLE_PRO_PLUS: - tuners.add(new MixerTunerDataLine(tdl, mixerTunerType)); - break; + if(info instanceof DataLine.Info) + { + try + { + Line line = mixer.getLine(info); + + if(line instanceof TargetDataLine) + { + return (TargetDataLine)line; + } + } + catch(LineUnavailableException lue) + { + mLog.error("Line Unavailable. Unable to get TargetDataLine for Mixer Tuner: " + mixerTunerType); + } + } } } } } - return tuners; + return null; + } + + /** + * Finds the target data line supported format that most closely matches the specified audio format without + * matching sample rates. + * @param info for a data line + * @param audioFormat to find a match + * @return matching audio format or null. + */ + public static AudioFormat getMatchingFormat(DataLine.Info info, AudioFormat audioFormat) + { + for(AudioFormat audioFormatToTest: info.getFormats()) + { + if(audioFormat.getSampleSizeInBits() == audioFormatToTest.getSampleSizeInBits() && + audioFormat.getChannels() == audioFormatToTest.getChannels() && + !(audioFormat.isBigEndian() ^ audioFormatToTest.isBigEndian())) + { + return audioFormatToTest; + } + } + + return null; } private static TargetDataLine getTargetDataLine(Mixer.Info mixerInfo, AudioFormat format) @@ -232,6 +269,20 @@ private static TargetDataLine getTargetDataLine(Mixer.Info mixerInfo, AudioForma { try { + for(Line line: mixer.getTargetLines()) + { + mLog.debug("Line: " + line.getLineInfo().toString()); + } + + for(Line line: mixer.getSourceLines()) + { + mLog.debug("Line: " + line.getLineInfo().toString()); + } + + Mixer.Info info = mixer.getMixerInfo(); + + mLog.debug(info.toString()); + DataLine.Info datalineInfo = new DataLine.Info(TargetDataLine.class, format); retVal = (TargetDataLine)mixer.getLine(datalineInfo); @@ -318,6 +369,11 @@ public static String getMixerDevices() .append("\n"); Mixer mixer = AudioSystem.getMixer(mixerInfo); + Line.Info lineInfo1 = mixer.getLineInfo(); + + String a = lineInfo1.toString(); + + Line.Info[] infos = mixer.getTargetLineInfo(); Line.Info[] sourceLines = mixer.getSourceLineInfo(); @@ -337,6 +393,41 @@ public static String getMixerDevices() .append("\n class:").append(lineInfo.getClass()) .append("\n lineclass:").append(lineInfo.getLineClass()) .append("\n"); + + if(lineInfo instanceof DataLine.Info) + { + DataLine.Info dli = (DataLine.Info)lineInfo; + + for(AudioFormat format: dli.getFormats()) + { + sb.append(" FORMAT:").append(format.toString()).append("\n"); + + if(mixerInfo.getName().startsWith("V")) + { + sb.append("Iterating formats for " + mixerInfo.getName() + " " + mixerInfo.getDescription()).append("\n"); + try + { + Line line = mixer.getLine(lineInfo); + sb.append(line.getLineInfo().toString()).append("\n"); + + if(line instanceof TargetDataLine) + { + TargetDataLine tdl = (TargetDataLine)line; + tdl.open(); + tdl.start(); + byte[] bytes = new byte[1024]; + int read = tdl.read(bytes, 0, bytes.length); + sb.append("READ:" + read).append("\n"); + tdl.close(); + } + } + catch(Exception e) + { + e.printStackTrace(); + } + } + } + } } Line.Info portInfo = new Line.Info(Port.class); @@ -389,9 +480,35 @@ public String toString() public static void main(String[] args) { - for(MixerChannelConfiguration config : getOutputMixers()) + MixerTunerType mixerTunerType = MixerTunerType.FUNCUBE_DONGLE_PRO_PLUS; + TargetDataLine targetDataLine = getTunerTargetDataLine(mixerTunerType); + + if(targetDataLine != null) { - mLog.debug(config.toString()); + try + { + targetDataLine.open(mixerTunerType.getAudioFormat()); + mLog.info(targetDataLine.getFormat().toString()); + targetDataLine.start(); + byte[] bytes = new byte[1024]; + int read = targetDataLine.read(bytes, 0, bytes.length); + + while(read > 0) + { + mLog.debug("Read:" + read); + read = 0; +// read = targetDataLine.read(bytes, 0, bytes.length); + } + + targetDataLine.close(); + + mLog.debug("Done!"); + } + catch(Exception e) + { + e.printStackTrace(); + } } + } } diff --git a/src/main/java/io/github/dsheirer/source/mixer/MixerReader.java b/src/main/java/io/github/dsheirer/source/mixer/MixerReader.java index 1cfaa45e9..351d9a4c2 100644 --- a/src/main/java/io/github/dsheirer/source/mixer/MixerReader.java +++ b/src/main/java/io/github/dsheirer/source/mixer/MixerReader.java @@ -66,10 +66,9 @@ public MixerReader(AudioFormat audioFormat, TargetDataLine targetDataLine, mBufferSize = (int)(mAudioFormat.getSampleRate() * 0.1) * mAudioFormat.getFrameSize(); } - public MixerReader(AudioFormat audioFormat, TargetDataLine targetDataLine, - AbstractSampleAdapter abstractSampleAdapter) + public MixerReader(AudioFormat audioFormat, TargetDataLine targetDataLine, AbstractSampleAdapter abstractSampleAdapter) { - this(audioFormat, targetDataLine, abstractSampleAdapter, null); + this(audioFormat, targetDataLine, abstractSampleAdapter, new HeartbeatManager()); } /** @@ -104,8 +103,12 @@ private void openTargetDataLine() throws LineUnavailableException mBuffer = new byte[mBufferSize]; - mTargetDataLine.open(mAudioFormat); + mTargetDataLine.open(getAudioFormat()); + mLog.info("TDL Open:" + mTargetDataLine.isOpen() + " Active:" + mTargetDataLine.isActive() + " Running:" + mTargetDataLine.isRunning()); + mLog.info("Format:" + mTargetDataLine.getFormat().toString()); mTargetDataLine.start(); + mLog.info("TDL Open:" + mTargetDataLine.isOpen() + " Active:" + mTargetDataLine.isActive() + " Running:" + mTargetDataLine.isRunning()); + } /** @@ -115,7 +118,15 @@ private void closeTargetDataLine() { if(mTargetDataLine != null && mTargetDataLine.isOpen()) { - mTargetDataLine.close(); + if(mTargetDataLine.isRunning()) + { + mTargetDataLine.stop(); + } + + if(mTargetDataLine.isOpen()) + { + mTargetDataLine.close(); + } } } @@ -215,9 +226,9 @@ else if(mBytesRead > 0) } } } - catch(Exception e) + catch(Throwable t) { - mLog.error("MixerSource - error while reading from the mixer target data line", e); + mLog.error("MixerSource - error while reading from the mixer target data line", t); stop(); } } diff --git a/src/main/java/io/github/dsheirer/source/tuner/MixerTunerDataLine.java b/src/main/java/io/github/dsheirer/source/tuner/MixerTunerDataLine.java deleted file mode 100644 index 7075f0c4d..000000000 --- a/src/main/java/io/github/dsheirer/source/tuner/MixerTunerDataLine.java +++ /dev/null @@ -1,42 +0,0 @@ -/******************************************************************************* - * 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 io.github.dsheirer.source.tuner; - -import javax.sound.sampled.TargetDataLine; - -public class MixerTunerDataLine -{ - private TargetDataLine mTargetDataLine; - protected MixerTunerType mMixerTunerType; - - public MixerTunerDataLine( TargetDataLine line, MixerTunerType type ) - { - mTargetDataLine = line; - mMixerTunerType = type; - } - - public TargetDataLine getTargetDataLine() - { - return mTargetDataLine; - } - - public MixerTunerType getMixerTunerType() - { - return mMixerTunerType; - } -} diff --git a/src/main/java/io/github/dsheirer/source/tuner/TunerManager.java b/src/main/java/io/github/dsheirer/source/tuner/TunerManager.java index 494140ab1..54955a2d7 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/TunerManager.java +++ b/src/main/java/io/github/dsheirer/source/tuner/TunerManager.java @@ -43,8 +43,8 @@ import org.usb4java.DeviceList; import org.usb4java.LibUsb; +import javax.sound.sampled.TargetDataLine; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; @@ -285,13 +285,11 @@ private void validateUSBBusTransferRates() } } - private TunerInitStatus initTuner(Device device, - DeviceDescriptor descriptor) + private TunerInitStatus initTuner(Device device, DeviceDescriptor descriptor) { if(device != null && descriptor != null) { - TunerClass tunerClass = TunerClass.valueOf(descriptor.idVendor(), - descriptor.idProduct()); + TunerClass tunerClass = TunerClass.valueOf(descriptor.idVendor(), descriptor.idProduct()); switch(tunerClass) { @@ -358,8 +356,7 @@ public void releaseTuners() } } - private TunerInitStatus initAirspyTuner(Device device, - DeviceDescriptor descriptor) + private TunerInitStatus initAirspyTuner(Device device, DeviceDescriptor descriptor) { try { @@ -381,35 +378,30 @@ private TunerInitStatus initAirspyTuner(Device device, } - private TunerInitStatus initEttusB100Tuner(Device device, - DeviceDescriptor descriptor) + private TunerInitStatus initEttusB100Tuner(Device device, DeviceDescriptor descriptor) { - return new TunerInitStatus(null, "Ettus B100 tuner not currently " - + "supported"); + return new TunerInitStatus(null, "Ettus B100 tuner not currently supported"); } private TunerInitStatus initFuncubeProTuner(Device device, DeviceDescriptor descriptor) { String reason = "NOT LOADED"; - MixerTunerDataLine dataline = getMixerTunerDataLine(TunerClass.FUNCUBE_DONGLE_PRO.getTunerType()); + TargetDataLine tdl = MixerManager.getTunerTargetDataLine(MixerTunerType.FUNCUBE_DONGLE_PRO); - if(dataline != null) + if(tdl != null) { - FCD1TunerController controller = new FCD1TunerController(dataline, device, descriptor); + FCD1TunerController controller = new FCD1TunerController(tdl, device, descriptor); try { controller.init(); - FCDTuner tuner = new FCDTuner(controller, mUserPreferences); - return new TunerInitStatus(tuner, "LOADED"); } catch(SourceException e) { mLog.error("couldn't load funcube dongle pro tuner", e); - reason = "error during initialization - " + e.getLocalizedMessage(); } } @@ -418,15 +410,14 @@ private TunerInitStatus initFuncubeProTuner(Device device, DeviceDescriptor desc reason = "couldn't find matching mixer dataline"; } - return new TunerInitStatus(null, "Funcube Dongle Pro tuner not " - + "loaded - " + reason); + return new TunerInitStatus(null, "Funcube Dongle Pro tuner not loaded - " + reason); } private TunerInitStatus initFuncubeProPlusTuner(Device device, DeviceDescriptor descriptor) { String reason = "NOT LOADED"; - MixerTunerDataLine dataline = getMixerTunerDataLine(TunerClass.FUNCUBE_DONGLE_PRO_PLUS.getTunerType()); + TargetDataLine dataline = MixerManager.getTunerTargetDataLine(MixerTunerType.FUNCUBE_DONGLE_PRO_PLUS); if(dataline != null) { @@ -435,17 +426,13 @@ private TunerInitStatus initFuncubeProPlusTuner(Device device, DeviceDescriptor try { controller.init(); - FCDTuner tuner = new FCDTuner(controller, mUserPreferences); - return new TunerInitStatus(tuner, "LOADED"); } catch(SourceException e) { mLog.error("couldn't load funcube dongle pro plus tuner", e); - - reason = "error during initialization - " + - e.getLocalizedMessage(); + reason = "error during initialization - " + e.getLocalizedMessage(); } } else @@ -453,8 +440,7 @@ private TunerInitStatus initFuncubeProPlusTuner(Device device, DeviceDescriptor reason = "couldn't find matching mixer dataline"; } - return new TunerInitStatus(null, "Funcube Dongle Pro tuner not " - + "loaded - " + reason); + return new TunerInitStatus(null, "Funcube Dongle Pro tuner not loaded - " + reason); } private TunerInitStatus initHackRFTuner(Device device, DeviceDescriptor descriptor) @@ -473,14 +459,11 @@ private TunerInitStatus initHackRFTuner(Device device, DeviceDescriptor descript { mLog.error("couldn't construct HackRF controller/tuner", se); - return new TunerInitStatus(null, - "error constructing HackRF tuner controller"); + return new TunerInitStatus(null, "error constructing HackRF tuner controller"); } } - private TunerInitStatus initRTL2832Tuner(TunerClass tunerClass, - Device device, - DeviceDescriptor deviceDescriptor) + private TunerInitStatus initRTL2832Tuner(TunerClass tunerClass, Device device, DeviceDescriptor deviceDescriptor) { String reason = "NOT LOADED"; @@ -540,8 +523,7 @@ private TunerInitStatus initRTL2832Tuner(TunerClass tunerClass, case RAFAELMICRO_R828D: case UNKNOWN: default: - reason = "SDRTRunk doesn't currently support RTL2832 " - + "Dongle with [" + tunerType.toString() + + reason = "SDRTRunk doesn't currently support RTL2832 Dongle with [" + tunerType.toString() + "] tuner for tuner class[" + tunerClass.toString() + "]"; break; } @@ -549,32 +531,6 @@ private TunerInitStatus initRTL2832Tuner(TunerClass tunerClass, return new TunerInitStatus(null, reason); } - /** - * Gets the first tuner mixer dataline that corresponds to the tuner class. - * - * Note: this method is not currently able to align multiple tuner mixer - * data lines of the same tuner type. If you have multiple Funcube Dongle - * tuners of the same TYPE, there is no guarantee that you will get the - * correct mixer. - * - * @param tunerClass - * @return - */ - private MixerTunerDataLine getMixerTunerDataLine(TunerType tunerClass) - { - Collection datalines = MixerManager.getMixerTunerDataLines(); - - for(MixerTunerDataLine mixerTDL : datalines) - { - if(mixerTDL.getMixerTunerType().getTunerClass() == tunerClass) - { - return mixerTDL; - } - } - - return null; - } - public class TunerInitStatus { private Tuner mTuner; diff --git a/src/main/java/io/github/dsheirer/source/tuner/fcd/FCDTunerController.java b/src/main/java/io/github/dsheirer/source/tuner/fcd/FCDTunerController.java index e66cbaa4e..8d45cf45a 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/fcd/FCDTunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/fcd/FCDTunerController.java @@ -23,7 +23,7 @@ import io.github.dsheirer.sample.buffer.ReusableComplexBuffer; import io.github.dsheirer.source.SourceException; import io.github.dsheirer.source.mixer.ComplexMixer; -import io.github.dsheirer.source.tuner.MixerTunerDataLine; +import io.github.dsheirer.source.tuner.MixerTunerType; import io.github.dsheirer.source.tuner.TunerClass; import io.github.dsheirer.source.tuner.TunerController; import io.github.dsheirer.source.tuner.TunerType; @@ -36,7 +36,7 @@ import org.usb4java.LibUsb; import org.usb4java.LibUsbException; -import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.TargetDataLine; import javax.usb.UsbClaimException; import javax.usb.UsbException; import java.nio.ByteBuffer; @@ -71,8 +71,8 @@ public abstract class FCDTunerController extends TunerController * @param minTunableFrequency * @param maxTunableFrequency */ - public FCDTunerController(MixerTunerDataLine mixerTDL, Device device, DeviceDescriptor descriptor, String tunerName, - int minTunableFrequency, int maxTunableFrequency, AudioFormat audioFormat) + public FCDTunerController(MixerTunerType tunerType, TargetDataLine mixerTDL, Device device, + DeviceDescriptor descriptor, int minTunableFrequency, int maxTunableFrequency) { super(minTunableFrequency, maxTunableFrequency, DC_SPIKE_AVOID_BUFFER, USABLE_BANDWIDTH_PERCENT); mDevice = device; @@ -80,17 +80,17 @@ public FCDTunerController(MixerTunerDataLine mixerTDL, Device device, DeviceDesc try { - mFrequencyController.setSampleRate((int)audioFormat.getSampleRate()); + mFrequencyController.setSampleRate((int)tunerType.getAudioFormat().getSampleRate()); } catch(SourceException se) { - mLog.error("Error setting sample rate to [" + audioFormat.getSampleRate() + "]", se); + mLog.error("Error setting sample rate to [" + tunerType.getAudioFormat().getSampleRate() + "]", se); } - mComplexMixer = new ComplexMixer( mixerTDL.getTargetDataLine(), audioFormat, tunerName, - new ComplexShortAdapter(mixerTDL.getMixerTunerType().getDisplayString())); + mComplexMixer = new ComplexMixer( mixerTDL, tunerType.getAudioFormat(), tunerType.getDisplayString(), + new ComplexShortAdapter(tunerType.getDisplayString())); - mComplexMixer.setBufferSize(audioFormat.getFrameSize() * getBufferSampleCount()); + mComplexMixer.setBufferSize(tunerType.getAudioFormat().getFrameSize() * getBufferSampleCount()); mComplexMixer.setBufferListener(mReusableBufferBroadcaster); } diff --git a/src/main/java/io/github/dsheirer/source/tuner/fcd/proV1/FCD1TunerController.java b/src/main/java/io/github/dsheirer/source/tuner/fcd/proV1/FCD1TunerController.java index 3049014a5..4e7a49c4d 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/fcd/proV1/FCD1TunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/fcd/proV1/FCD1TunerController.java @@ -18,7 +18,6 @@ package io.github.dsheirer.source.tuner.fcd.proV1; import io.github.dsheirer.source.SourceException; -import io.github.dsheirer.source.tuner.MixerTunerDataLine; import io.github.dsheirer.source.tuner.MixerTunerType; import io.github.dsheirer.source.tuner.TunerClass; import io.github.dsheirer.source.tuner.TunerType; @@ -30,6 +29,7 @@ import org.usb4java.Device; import org.usb4java.DeviceDescriptor; +import javax.sound.sampled.TargetDataLine; import javax.usb.UsbClaimException; import javax.usb.UsbException; import java.nio.ByteBuffer; @@ -53,10 +53,10 @@ public class FCD1TunerController extends FCDTunerController private FCD1TunerConfiguration mTunerConfiguration; private FCD1TunerEditor mEditor; - public FCD1TunerController(MixerTunerDataLine mixerTDL, Device device, DeviceDescriptor descriptor) + public FCD1TunerController(TargetDataLine mixerTDL, Device device, DeviceDescriptor descriptor) { - super(mixerTDL, device, descriptor, MixerTunerType.FUNCUBE_DONGLE_PRO.getDisplayString(), MINIMUM_TUNABLE_FREQUENCY, - MAXIMUM_TUNABLE_FREQUENCY, MixerTunerType.FUNCUBE_DONGLE_PRO.getAudioFormat()); + super(MixerTunerType.FUNCUBE_DONGLE_PRO, mixerTDL, device, descriptor, + MINIMUM_TUNABLE_FREQUENCY, MAXIMUM_TUNABLE_FREQUENCY); } public void init() throws SourceException @@ -78,10 +78,7 @@ public void init() throws SourceException } catch(Exception e) { - e.printStackTrace(); - - throw new SourceException("FCDTunerController error " + - "during construction", e); + throw new SourceException("FCDTunerController error during construction", e); } } diff --git a/src/main/java/io/github/dsheirer/source/tuner/fcd/proplusV2/FCD2TunerController.java b/src/main/java/io/github/dsheirer/source/tuner/fcd/proplusV2/FCD2TunerController.java index dfb81b677..2754de446 100644 --- a/src/main/java/io/github/dsheirer/source/tuner/fcd/proplusV2/FCD2TunerController.java +++ b/src/main/java/io/github/dsheirer/source/tuner/fcd/proplusV2/FCD2TunerController.java @@ -19,7 +19,6 @@ package io.github.dsheirer.source.tuner.fcd.proplusV2; import io.github.dsheirer.source.SourceException; -import io.github.dsheirer.source.tuner.MixerTunerDataLine; import io.github.dsheirer.source.tuner.MixerTunerType; import io.github.dsheirer.source.tuner.TunerClass; import io.github.dsheirer.source.tuner.TunerType; @@ -31,6 +30,7 @@ import org.usb4java.Device; import org.usb4java.DeviceDescriptor; +import javax.sound.sampled.TargetDataLine; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -42,10 +42,10 @@ public class FCD2TunerController extends FCDTunerController public static final int MAXIMUM_TUNABLE_FREQUENCY = 2050000000; public static final int SAMPLE_RATE = 192000; - public FCD2TunerController(MixerTunerDataLine mixerTDL, Device device, DeviceDescriptor descriptor) + public FCD2TunerController(TargetDataLine mixerTDL, Device device, DeviceDescriptor descriptor) { - super(mixerTDL, device, descriptor, MixerTunerType.FUNCUBE_DONGLE_PRO_PLUS.getDisplayString(), - MINIMUM_TUNABLE_FREQUENCY, MAXIMUM_TUNABLE_FREQUENCY, MixerTunerType.FUNCUBE_DONGLE_PRO_PLUS.getAudioFormat()); + super(MixerTunerType.FUNCUBE_DONGLE_PRO_PLUS, mixerTDL, device, descriptor, + MINIMUM_TUNABLE_FREQUENCY, MAXIMUM_TUNABLE_FREQUENCY); } public void init() throws SourceException