Add software

This commit is contained in:
RocketGod
2022-09-22 09:26:57 -07:00
parent fee0ab05fd
commit 957ea3d712
4511 changed files with 1943182 additions and 0 deletions

View File

@ -0,0 +1,973 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "AppConfig.h"
#include "CubicSDR.h"
#include <wx/msgdlg.h>
DeviceConfig::DeviceConfig() {
ppm.store(0);
offset.store(0);
agcMode.store(true);
sampleRate.store(0);
}
DeviceConfig::DeviceConfig(std::string deviceId_in) : DeviceConfig() {
deviceId = deviceId_in;
}
void DeviceConfig::setPPM(int ppm_in) {
ppm.store(ppm_in);
}
int DeviceConfig::getPPM() {
return ppm.load();
}
void DeviceConfig::setOffset(long long offset_in) {
offset.store(offset_in);
}
long long DeviceConfig::getOffset() {
return offset.load();
}
void DeviceConfig::setSampleRate(long sampleRate_in) {
sampleRate.store(sampleRate_in);
}
long DeviceConfig::getSampleRate() {
return sampleRate.load();
}
void DeviceConfig::setAntennaName(const std::string& name) {
antennaName = name;
}
const std::string& DeviceConfig::getAntennaName() {
return antennaName;
}
void DeviceConfig::setAGCMode(bool agcMode_in) {
agcMode.store(agcMode_in);
}
bool DeviceConfig::getAGCMode() {
return agcMode.load();
}
void DeviceConfig::setDeviceId(std::string deviceId_in) {
std::lock_guard < std::mutex > lock(busy_lock);
deviceId = deviceId_in;
}
std::string DeviceConfig::getDeviceId() {
std::string tmp;
std::lock_guard < std::mutex > lock(busy_lock);
tmp = deviceId;
return tmp;
}
void DeviceConfig::setDeviceName(std::string deviceName_in) {
std::lock_guard < std::mutex > lock(busy_lock);
deviceName = deviceName_in;
}
std::string DeviceConfig::getDeviceName() {
std::string tmp;
std::lock_guard < std::mutex > lock(busy_lock);
tmp = deviceName.empty()?deviceId:deviceName;
return tmp;
}
void DeviceConfig::save(DataNode *node) {
std::lock_guard < std::mutex > lock(busy_lock);
*node->newChild("id") = deviceId;
*node->newChild("name") = deviceName;
*node->newChild("ppm") = ppm.load();
*node->newChild("offset") = offset.load();
*node->newChild("sample_rate") = sampleRate.load();
*node->newChild("agc_mode") = agcMode.load()?1:0;
if (!antennaName.empty()) {
*node->newChild("antenna") = antennaName;
}
if (!streamOpts.empty()) {
DataNode *streamOptsNode = node->newChild("streamOpts");
for (ConfigSettings::const_iterator opt_i = streamOpts.begin(); opt_i != streamOpts.end(); opt_i++) {
*streamOptsNode->newChild(opt_i->first.c_str()) = opt_i->second;
}
}
if (!settings.empty()) {
DataNode *settingsNode = node->newChild("settings");
for (ConfigSettings::const_iterator set_i = settings.begin(); set_i != settings.end(); set_i++) {
*settingsNode->newChild(set_i->first.c_str()) = set_i->second;
}
}
if (!rigIF.empty()) {
DataNode *rigIFs = node->newChild("rig_ifs");
for (std::map<int, long long>::const_iterator rigIF_i = rigIF.begin(); rigIF_i != rigIF.end(); rigIF_i++) {
DataNode *ifNode = rigIFs->newChild("rig_if");
*ifNode->newChild("model") = rigIF_i->first;
*ifNode->newChild("sdr_if") = rigIF_i->second;
}
}
if (!gains.empty()) {
DataNode *gainsNode = node->newChild("gains");
for (ConfigGains::const_iterator gain_i = gains.begin(); gain_i != gains.end(); gain_i++) {
DataNode *gainNode = gainsNode->newChild("gain");
*gainNode->newChild("id") = gain_i->first;
*gainNode->newChild("value") = gain_i->second;
}
}
}
void DeviceConfig::load(DataNode *node) {
std::lock_guard < std::mutex > lock(busy_lock);
if (node->hasAnother("name")) {
deviceName = node->getNext("name")->element()->toString();
}
if (node->hasAnother("ppm")) {
DataNode *ppm_node = node->getNext("ppm");
int ppmValue = 0;
ppm_node->element()->get(ppmValue);
setPPM(ppmValue);
}
if (node->hasAnother("offset")) {
DataNode *offset_node = node->getNext("offset");
long long offsetValue = 0;
offset_node->element()->get(offsetValue);
setOffset(offsetValue);
}
if (node->hasAnother("agc_mode")) {
DataNode *agc_node = node->getNext("agc_mode");
int agcModeValue = 0;
agc_node->element()->get(agcModeValue);
setAGCMode(agcModeValue != 0);
}
if (node->hasAnother("sample_rate")) {
DataNode *sample_rate_node = node->getNext("sample_rate");
long sampleRateValue = 0;
sample_rate_node->element()->get(sampleRateValue);
setSampleRate(sampleRateValue);
}
if (node->hasAnother("antenna")) {
DataNode *antenna_node = node->getNext("antenna");
std::string antennaNameValue;
antenna_node->element()->get(antennaNameValue);
setAntennaName(antennaNameValue);
}
if (node->hasAnother("streamOpts")) {
DataNode *streamOptsNode = node->getNext("streamOpts");
for (int i = 0, iMax = streamOptsNode->numChildren(); i<iMax; i++) {
DataNode *streamOptNode = streamOptsNode->child(i);
std::string keyName = streamOptNode->getName();
std::string strSettingValue = streamOptNode->element()->toString();
if (!keyName.empty()) {
setStreamOpt(keyName, strSettingValue);
}
}
}
if (node->hasAnother("settings")) {
DataNode *settingsNode = node->getNext("settings");
for (int i = 0, iMax = settingsNode->numChildren(); i<iMax; i++) {
DataNode *settingNode = settingsNode->child(i);
std::string keyName = settingNode->getName();
std::string strSettingValue = settingNode->element()->toString();
if (!keyName.empty()) {
setSetting(keyName, strSettingValue);
}
}
}
if (node->hasAnother("rig_ifs")) {
DataNode *rigIFNodes = node->getNext("rig_ifs");
while (rigIFNodes->hasAnother("rig_if")) {
DataNode *rigIFNode = rigIFNodes->getNext("rig_if");
if (rigIFNode->hasAnother("model") && rigIFNode->hasAnother("sdr_if")) {
int load_model;
long long load_freq = 0;
rigIFNode->getNext("model")->element()->get(load_model);
rigIFNode->getNext("sdr_if")->element()->get(load_freq);
rigIF[load_model] = load_freq;
}
}
}
if (node->hasAnother("gains")) {
DataNode *gainsNode = node->getNext("gains");
while (gainsNode->hasAnother("gain")) {
DataNode *gainNode = gainsNode->getNext("gain");
std::string keyName;
float fltSettingValue = 0;
gainNode->getNext("id")->element()->get(keyName);
gainNode->getNext("value")->element()->get(fltSettingValue);
if (!keyName.empty() && !(fltSettingValue!=fltSettingValue)) {
setGain(keyName, fltSettingValue);
}
}
}
}
void DeviceConfig::setStreamOpts(ConfigSettings opts) {
streamOpts = opts;
}
ConfigSettings DeviceConfig::getStreamOpts() {
return streamOpts;
}
void DeviceConfig::setStreamOpt(const std::string& key, std::string value) {
streamOpts[key] = value;
}
std::string DeviceConfig::getStreamOpt(const std::string& key, std::string defaultValue) {
if (streamOpts.find(key) == streamOpts.end()) {
return defaultValue;
}
return streamOpts[key];
}
void DeviceConfig::setSettings(ConfigSettings settings_in) {
settings = settings_in;
}
void DeviceConfig::setSetting(const std::string& key, std::string value) {
settings[key] = value;
}
std::string DeviceConfig::getSetting(const std::string& key, std::string defaultValue) {
if (settings.find(key) == settings.end()) {
return defaultValue;
}
return settings[key];
}
ConfigSettings DeviceConfig::getSettings() {
return settings;
}
void DeviceConfig::setGains(ConfigGains gains_in) {
gains = gains_in;
}
ConfigGains DeviceConfig::getGains() {
return gains;
}
void DeviceConfig::setGain(const std::string& key, float value) {
gains[key] = value;
}
float DeviceConfig::getGain(const std::string& key, float defaultValue) {
if (gains.find(key) != gains.end()) {
return gains[key];
}
return defaultValue;
}
void DeviceConfig::setRigIF(int rigType, long long freq) {
rigIF[rigType] = freq;
}
long long DeviceConfig::getRigIF(int rigType) {
if (rigIF.find(rigType) != rigIF.end()) {
return rigIF[rigType];
}
return 0;
}
AppConfig::AppConfig() {
winX.store(0);
winY.store(0);
winW.store(0);
winH.store(0);
winMax.store(false);
showTips.store(true);
perfMode.store(PERF_NORMAL);
themeId.store(0);
fontScale.store(0);
snap.store(1);
centerFreq.store(100000000);
waterfallLinesPerSec.store(DEFAULT_WATERFALL_LPS);
spectrumAvgSpeed.store(0.65f);
dbOffset.store(0);
modemPropsCollapsed.store(false);
mainSplit = -1;
visSplit = -1;
bookmarkSplit = 200;
#ifdef CUBICSDR_DEFAULT_HIDE_BOOKMARKS
bookmarksVisible.store(false);
#else
bookmarksVisible.store(true);
#endif
#ifdef USE_HAMLIB
rigEnabled.store(false);
rigModel.store(1);
rigRate.store(57600);
rigPort = "/dev/ttyUSB0";
rigControlMode.store(true);
rigFollowMode.store(true);
#endif
}
DeviceConfig *AppConfig::getDevice(const std::string& deviceId) {
if (deviceConfig.find(deviceId) == deviceConfig.end()) {
deviceConfig[deviceId] = new DeviceConfig();
}
DeviceConfig *conf = deviceConfig[deviceId];
conf->setDeviceId(deviceId);
return conf;
}
std::string AppConfig::getConfigDir() {
std::string dataDir = wxStandardPaths::Get().GetUserDataDir().ToStdString();
bool mkStatus = false;
if (!wxDir::Exists(dataDir)) {
mkStatus = wxDir::Make(dataDir);
} else {
mkStatus = true;
}
if (!mkStatus) {
std::cout << "Warning, unable to initialize user data directory." << std::endl;
}
return dataDir;
}
void AppConfig::setWindow(wxPoint winXY, wxSize winWH) {
winX.store(winXY.x);
winY.store(winXY.y);
winW.store(winWH.x);
winH.store(winWH.y);
}
void AppConfig::setWindowMaximized(bool max) {
winMax.store(max);
}
bool AppConfig::getWindowMaximized() {
return winMax.load();
}
void AppConfig::setModemPropsCollapsed(bool collapse) {
modemPropsCollapsed.store(collapse);
}
bool AppConfig::getModemPropsCollapsed() {
return modemPropsCollapsed.load();
}
void AppConfig::setShowTips(bool show) {
showTips.store(show);
}
bool AppConfig::getShowTips() {
return showTips.load();
}
void AppConfig::setPerfMode(PerfModeEnum show) {
perfMode.store(show);
}
AppConfig::PerfModeEnum AppConfig::getPerfMode() {
return perfMode.load();
}
wxRect *AppConfig::getWindow() {
wxRect *r = nullptr;
if (winH.load() && winW.load()) {
r = new wxRect(winX.load(),winY.load(),winW.load(),winH.load());
}
return r;
}
void AppConfig::setTheme(int themeId_in) {
themeId.store(themeId_in);
}
int AppConfig::getTheme() {
return themeId.load();
}
void AppConfig::setFontScale(int fontScale_in) {
fontScale.store(fontScale_in);
}
int AppConfig::getFontScale() {
return fontScale.load();
}
void AppConfig::setSnap(long long snapVal) {
this->snap.store(snapVal);
}
long long AppConfig::getSnap() {
return snap.load();
}
void AppConfig::setCenterFreq(long long freqVal) {
centerFreq.store(freqVal);
}
long long AppConfig::getCenterFreq() {
return centerFreq.load();
}
void AppConfig::setWaterfallLinesPerSec(int lps) {
waterfallLinesPerSec.store(lps);
}
int AppConfig::getWaterfallLinesPerSec() {
return waterfallLinesPerSec.load();
}
void AppConfig::setSpectrumAvgSpeed(float avgSpeed) {
spectrumAvgSpeed.store(avgSpeed);
}
float AppConfig::getSpectrumAvgSpeed() {
return spectrumAvgSpeed.load();
}
void AppConfig::setDBOffset(int offset) {
this->dbOffset.store(offset);
}
int AppConfig::getDBOffset() {
return dbOffset.load();
}
void AppConfig::setManualDevices(std::vector<SDRManualDef> manuals) {
manualDevices = manuals;
}
std::vector<SDRManualDef> AppConfig::getManualDevices() {
return manualDevices;
}
void AppConfig::setMainSplit(float value) {
mainSplit.store(value);
}
float AppConfig::getMainSplit() {
return mainSplit.load();
}
void AppConfig::setVisSplit(float value) {
visSplit.store(value);
}
float AppConfig::getVisSplit() {
return visSplit.load();
}
void AppConfig::setBookmarkSplit(float value) {
bookmarkSplit.store(value);
}
float AppConfig::getBookmarkSplit() {
return bookmarkSplit.load();
}
void AppConfig::setBookmarksVisible(bool state) {
bookmarksVisible.store(state);
}
bool AppConfig::getBookmarksVisible() {
return bookmarksVisible.load();
}
void AppConfig::setRecordingPath(std::string recPath) {
recordingPath = recPath;
}
std::string AppConfig::getRecordingPath() {
return recordingPath;
}
bool AppConfig::verifyRecordingPath() {
string recPathStr = wxGetApp().getConfig()->getRecordingPath();
if (recPathStr.empty()) {
wxMessageBox( wxT("Recording path is not set. Please use 'Set Recording Path' from the 'Recording' Menu."), wxT("Recording Path Error"), wxICON_INFORMATION);
return false;
}
wxFileName recPath(recPathStr);
if (!recPath.Exists() || !recPath.IsDirWritable()) {
wxMessageBox( wxT("Recording path does not exist or is not writable. Please use 'Set Recording Path' from the 'Recording' Menu."), wxT("Recording Path Error"), wxICON_INFORMATION);
return false;
}
return true;
}
void AppConfig::setRecordingSquelchOption(int enumChoice) {
recordingSquelchOption = enumChoice;
}
int AppConfig::getRecordingSquelchOption() const {
return recordingSquelchOption;
}
void AppConfig::setRecordingFileTimeLimit(int nbSeconds) {
recordingFileTimeLimitSeconds = nbSeconds;
}
int AppConfig::getRecordingFileTimeLimit() const {
return recordingFileTimeLimitSeconds;
}
void AppConfig::setConfigName(std::string configName_in) {
configName = configName_in;
}
std::string AppConfig::getConfigFileName(bool ignoreName) {
std::string cfgFileDir = getConfigDir();
wxFileName cfgFile;
if (configName.length() && !ignoreName) {
std::string tempFn("config-");
tempFn.append(configName);
tempFn.append(".xml");
cfgFile = wxFileName(cfgFileDir, tempFn);
} else {
cfgFile = wxFileName(cfgFileDir, "config.xml");
}
std::string cfgFileName = cfgFile.GetFullPath(wxPATH_NATIVE).ToStdString();
return cfgFileName;
}
bool AppConfig::save() {
DataTree cfg;
cfg.rootNode()->setName("cubicsdr_config");
if (winW.load() && winH.load()) {
DataNode *window_node = cfg.rootNode()->newChild("window");
*window_node->newChild("x") = winX.load();
*window_node->newChild("y") = winY.load();
*window_node->newChild("w") = winW.load();
*window_node->newChild("h") = winH.load();
*window_node->newChild("max") = winMax.load();
*window_node->newChild("tips") = showTips.load();
*window_node->newChild("perf_mode") = (int)perfMode.load();
*window_node->newChild("theme") = themeId.load();
*window_node->newChild("font_scale") = fontScale.load();
*window_node->newChild("snap") = snap.load();
*window_node->newChild("center_freq") = centerFreq.load();
*window_node->newChild("waterfall_lps") = waterfallLinesPerSec.load();
*window_node->newChild("spectrum_avg") = spectrumAvgSpeed.load();
*window_node->newChild("modemprops_collapsed") = modemPropsCollapsed.load();
*window_node->newChild("db_offset") = dbOffset.load();
*window_node->newChild("main_split") = mainSplit.load();
*window_node->newChild("vis_split") = visSplit.load();
*window_node->newChild("bookmark_split") = bookmarkSplit.load();
*window_node->newChild("bookmark_visible") = bookmarksVisible.load();
}
//Recording settings:
DataNode *rec_node = cfg.rootNode()->newChild("recording");
*rec_node->newChild("path") = recordingPath;
*rec_node->newChild("squelch") = recordingSquelchOption;
*rec_node->newChild("file_time_limit") = recordingFileTimeLimitSeconds;
DataNode *devices_node = cfg.rootNode()->newChild("devices");
std::map<std::string, DeviceConfig *>::iterator device_config_i;
for (device_config_i = deviceConfig.begin(); device_config_i != deviceConfig.end(); device_config_i++) {
DataNode *device_node = devices_node->newChild("device");
device_config_i->second->save(device_node);
}
if (!manualDevices.empty()) {
DataNode *manual_node = cfg.rootNode()->newChild("manual_devices");
for (const auto & manualDevice : manualDevices) {
DataNode *rig_node = manual_node->newChild("device");
*rig_node->newChild("factory") = manualDevice.factory;
*rig_node->newChild("params") = manualDevice.params;
}
}
#ifdef USE_HAMLIB
DataNode *rig_node = cfg.rootNode()->newChild("rig");
*rig_node->newChild("enabled") = rigEnabled.load()?1:0;
*rig_node->newChild("model") = rigModel.load();
*rig_node->newChild("rate") = rigRate.load();
*rig_node->newChild("port") = rigPort;
*rig_node->newChild("control") = rigControlMode.load()?1:0;
*rig_node->newChild("follow") = rigFollowMode.load()?1:0;
*rig_node->newChild("center_lock") = rigCenterLock.load()?1:0;
*rig_node->newChild("follow_modem") = rigFollowModem.load()?1:0;
#endif
std::string cfgFileName = getConfigFileName();
if (!cfg.SaveToFileXML(cfgFileName)) {
std::cout << "Error saving :: configuration file '" << cfgFileName << "' is not writable!" << std::endl;
return false;
}
return true;
}
bool AppConfig::load() {
DataTree cfg;
std::string cfgFileDir = getConfigDir();
std::string cfgFileName = getConfigFileName();
wxFileName cfgFile = wxFileName(cfgFileName);
if (!cfgFile.Exists()) {
if (configName.length()) {
wxFileName baseConfig = wxFileName(getConfigFileName(true));
if (baseConfig.Exists()) {
std::string baseConfigFileName = baseConfig.GetFullPath(wxPATH_NATIVE).ToStdString();
std::cout << "Creating new configuration file '" << cfgFileName << "' by copying '" << baseConfigFileName << "'..";
wxCopyFile(baseConfigFileName, cfgFileName);
if (!cfgFile.Exists()) {
std::cout << "failed." << std::endl;
return true;
}
std::cout << "ok." << std::endl;
} else {
return true;
}
} else {
return true;
}
}
if (cfgFile.IsFileReadable()) {
std::cout << "Loading:: configuration file '" << cfgFileName << "'" << std::endl;
cfg.LoadFromFileXML(cfgFileName);
} else {
std::cout << "Error loading:: configuration file '" << cfgFileName << "' is not readable!" << std::endl;
return false;
}
if (cfg.rootNode()->hasAnother("window")) {
int x = 0 ,y = 0 ,w = 0 ,h = 0;
int max = 0 ,tips = 0 ,perf_mode = 0 ,mpc = 0;
DataNode *win_node = cfg.rootNode()->getNext("window");
if (win_node->hasAnother("w") && win_node->hasAnother("h") && win_node->hasAnother("x") && win_node->hasAnother("y")) {
win_node->getNext("x")->element()->get(x);
win_node->getNext("y")->element()->get(y);
win_node->getNext("w")->element()->get(w);
win_node->getNext("h")->element()->get(h);
winX.store(x);
winY.store(y);
winW.store(w);
winH.store(h);
}
if (win_node->hasAnother("max")) {
win_node->getNext("max")->element()->get(max);
winMax.store(max != 0);
}
if (win_node->hasAnother("tips")) {
win_node->getNext("tips")->element()->get(tips);
showTips.store(tips != 0);
}
// default:
perfMode.store(PERF_NORMAL);
if (win_node->hasAnother("perf_mode")) {
win_node->getNext("perf_mode")->element()->get(perf_mode);
if (perf_mode == (int)PERF_LOW) {
perfMode.store(PERF_LOW);
} else if (perf_mode == (int)PERF_HIGH) {
perfMode.store(PERF_HIGH);
}
}
if (win_node->hasAnother("theme")) {
int theme = 0;
win_node->getNext("theme")->element()->get(theme);
themeId.store(theme);
}
if (win_node->hasAnother("font_scale")) {
int fscale = 0;
win_node->getNext("font_scale")->element()->get(fscale);
fontScale.store(fscale);
}
if (win_node->hasAnother("snap")) {
long long snapVal = 0;
win_node->getNext("snap")->element()->get(snapVal);
snap.store(snapVal);
}
if (win_node->hasAnother("center_freq")) {
long long freqVal = 0;
win_node->getNext("center_freq")->element()->get(freqVal);
centerFreq.store(freqVal);
}
if (win_node->hasAnother("waterfall_lps")) {
int lpsVal = 30;
win_node->getNext("waterfall_lps")->element()->get(lpsVal);
waterfallLinesPerSec.store(lpsVal);
}
if (win_node->hasAnother("spectrum_avg")) {
float avgVal;
win_node->getNext("spectrum_avg")->element()->get(avgVal);
spectrumAvgSpeed.store(avgVal);
}
if (win_node->hasAnother("modemprops_collapsed")) {
win_node->getNext("modemprops_collapsed")->element()->get(mpc);
modemPropsCollapsed.store(mpc != 0);
}
if (win_node->hasAnother("db_offset")) {
DataNode *offset_node = win_node->getNext("db_offset");
int offsetValue = 0;
offset_node->element()->get(offsetValue);
setDBOffset(offsetValue);
}
if (win_node->hasAnother("main_split")) {
float gVal;
win_node->getNext("main_split")->element()->get(gVal);
mainSplit.store(gVal);
}
if (win_node->hasAnother("vis_split")) {
float gVal;
win_node->getNext("vis_split")->element()->get(gVal);
visSplit.store(gVal);
}
if (win_node->hasAnother("bookmark_split")) {
float gVal;
win_node->getNext("bookmark_split")->element()->get(gVal);
bookmarkSplit.store(gVal);
}
if (win_node->hasAnother("bookmark_visible")) {
int bVal;
win_node->getNext("bookmark_visible")->element()->get(bVal);
bookmarksVisible.store(bVal);
}
}
//Recording settings:
if (cfg.rootNode()->hasAnother("recording")) {
DataNode *rec_node = cfg.rootNode()->getNext("recording");
if (rec_node->hasAnother("path")) {
DataNode *rec_path = rec_node->getNext("path");
recordingPath = rec_path->element()->toString();
}
if (rec_node->hasAnother("squelch")) {
DataNode *rec_squelch = rec_node->getNext("squelch");
rec_squelch->element()->get(recordingSquelchOption);
}
if (rec_node->hasAnother("file_time_limit")) {
DataNode *rec_file_time_limit = rec_node->getNext("file_time_limit");
rec_file_time_limit->element()->get(recordingFileTimeLimitSeconds);
}
}
if (cfg.rootNode()->hasAnother("devices")) {
DataNode *devices_node = cfg.rootNode()->getNext("devices");
while (devices_node->hasAnother("device")) {
DataNode *device_node = devices_node->getNext("device");
if (device_node->hasAnother("id")) {
std::string deviceId = device_node->getNext("id")->element()->toString();
getDevice(deviceId)->load(device_node);
}
}
}
if (cfg.rootNode()->hasAnother("manual_devices")) {
DataNode *manuals_node = cfg.rootNode()->getNext("manual_devices");
while (manuals_node->hasAnother("device")) {
DataNode *manual_node = manuals_node->getNext("device");
if (manual_node->hasAnother("factory") && manual_node->hasAnother("params")) {
SDRManualDef mdef;
mdef.factory = manual_node->getNext("factory")->element()->toString();
mdef.params = manual_node->getNext("params")->element()->toString();
manualDevices.push_back(mdef);
}
}
}
#ifdef USE_HAMLIB
if (cfg.rootNode()->hasAnother("rig")) {
DataNode *rig_node = cfg.rootNode()->getNext("rig");
if (rig_node->hasAnother("enabled")) {
int loadEnabled;
rig_node->getNext("enabled")->element()->get(loadEnabled);
rigEnabled.store(loadEnabled != 0);
}
if (rig_node->hasAnother("model")) {
int loadModel;
rig_node->getNext("model")->element()->get(loadModel);
rigModel.store(loadModel?loadModel:1);
}
if (rig_node->hasAnother("rate")) {
int loadRate;
rig_node->getNext("rate")->element()->get(loadRate);
rigRate.store(loadRate?loadRate:57600);
}
if (rig_node->hasAnother("port")) {
rigPort = rig_node->getNext("port")->element()->toString();
}
if (rig_node->hasAnother("control")) {
int loadControl;
rig_node->getNext("control")->element()->get(loadControl);
rigControlMode.store(loadControl != 0);
}
if (rig_node->hasAnother("follow")) {
int loadFollow;
rig_node->getNext("follow")->element()->get(loadFollow);
rigFollowMode.store(loadFollow != 0);
}
if (rig_node->hasAnother("center_lock")) {
int loadCenterLock;
rig_node->getNext("center_lock")->element()->get(loadCenterLock);
rigCenterLock.store(loadCenterLock != 0);
}
if (rig_node->hasAnother("follow_modem")) {
int loadFollow;
rig_node->getNext("follow_modem")->element()->get(loadFollow);
rigFollowModem.store(loadFollow != 0);
}
}
#endif
return true;
}
bool AppConfig::reset() {
return true;
}
#if USE_HAMLIB
int AppConfig::getRigModel() {
return rigModel.load();
}
void AppConfig::setRigModel(int rigModel_in) {
rigModel.store(rigModel_in);
}
int AppConfig::getRigRate() {
return rigRate.load();
}
void AppConfig::setRigRate(int rigRate_in) {
rigRate.store(rigRate_in);
}
std::string AppConfig::getRigPort() {
return rigPort;
}
void AppConfig::setRigPort(std::string rigPort_in) {
rigPort = rigPort_in;
}
void AppConfig::setRigControlMode(bool cMode) {
rigControlMode.store(cMode);
}
bool AppConfig::getRigControlMode() {
return rigControlMode.load();
}
void AppConfig::setRigFollowMode(bool fMode) {
rigFollowMode.store(fMode);
}
bool AppConfig::getRigFollowMode() {
return rigFollowMode.load();
}
void AppConfig::setRigCenterLock(bool cLock) {
rigCenterLock.store(cLock);
}
bool AppConfig::getRigCenterLock() {
return rigCenterLock.load();
}
void AppConfig::setRigFollowModem(bool fMode) {
rigFollowModem.store(fMode);
}
bool AppConfig::getRigFollowModem() {
return rigFollowModem.load();
}
void AppConfig::setRigEnabled(bool enabled) {
rigEnabled.store(enabled);
}
bool AppConfig::getRigEnabled() {
return rigEnabled.load();
}
#endif

View File

@ -0,0 +1,217 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include <wx/stdpaths.h>
#include <wx/dir.h>
#include <wx/filename.h>
#include <wx/panel.h>
#include <atomic>
#include <mutex>
#include "DataTree.h"
#include "CubicSDRDefs.h"
#include "SDRDeviceInfo.h"
typedef std::map<std::string, std::string> ConfigSettings;
typedef std::map<std::string, float> ConfigGains;
class DeviceConfig {
public:
DeviceConfig();
explicit DeviceConfig(std::string deviceId_in);
void setPPM(int ppm_in);
int getPPM();
void setOffset(long long offset_in);
long long getOffset();
void setSampleRate(long sampleRate_in);
long getSampleRate();
void setAntennaName(const std::string& name);
const std::string& getAntennaName();
void setAGCMode(bool agcMode_in);
bool getAGCMode();
void setDeviceId(std::string deviceId_in);
std::string getDeviceId();
void setDeviceName(std::string deviceName_in);
std::string getDeviceName();
void setStreamOpts(ConfigSettings opts);
ConfigSettings getStreamOpts();
void setStreamOpt(const std::string& key, std::string value);
std::string getStreamOpt(const std::string& key, std::string defaultValue);
void setSettings(ConfigSettings settings_in);
ConfigSettings getSettings();
void setSetting(const std::string& key, std::string value);
std::string getSetting(const std::string& key, std::string defaultValue);
void setGains(ConfigGains gains_in);
ConfigGains getGains();
void setGain(const std::string& key, float value);
float getGain(const std::string& key, float defaultValue);
void setRigIF(int rigType, long long freq);
long long getRigIF(int rigType);
void save(DataNode *node);
void load(DataNode *node);
private:
std::string deviceId;
std::string deviceName;
std::mutex busy_lock;
std::atomic_int ppm{};
std::atomic_llong offset{};
std::atomic_bool agcMode{};
std::atomic_long sampleRate{};
std::string antennaName;
ConfigSettings streamOpts;
ConfigGains gains;
std::map<std::string, std::string> settings;
std::map<int, long long> rigIF;
};
class AppConfig {
public:
enum PerfModeEnum {
PERF_LOW = 0,
PERF_NORMAL = 1,
PERF_HIGH = 2
};
AppConfig();
std::string getConfigDir();
DeviceConfig *getDevice(const std::string& deviceId);
void setWindow(wxPoint winXY, wxSize winWH);
wxRect *getWindow();
void setWindowMaximized(bool max);
bool getWindowMaximized();
void setModemPropsCollapsed(bool collapse);
bool getModemPropsCollapsed();
void setShowTips(bool show);
bool getShowTips();
void setPerfMode(PerfModeEnum mode);
PerfModeEnum getPerfMode();
void setTheme(int themeId_in);
int getTheme();
void setFontScale(int fontScale_in);
int getFontScale();
void setSnap(long long snapVal);
long long getSnap();
void setCenterFreq(long long freqVal);
long long getCenterFreq();
void setWaterfallLinesPerSec(int lps);
int getWaterfallLinesPerSec();
void setSpectrumAvgSpeed(float avgSpeed);
float getSpectrumAvgSpeed();
void setDBOffset(int offset);
int getDBOffset();
void setManualDevices(std::vector<SDRManualDef> manuals);
std::vector<SDRManualDef> getManualDevices();
void setMainSplit(float value);
float getMainSplit();
void setVisSplit(float value);
float getVisSplit();
void setBookmarkSplit(float value);
float getBookmarkSplit();
void setBookmarksVisible(bool state);
bool getBookmarksVisible();
//Recording settings:
void setRecordingPath(std::string recPath);
std::string getRecordingPath();
bool verifyRecordingPath();
void setRecordingSquelchOption(int enumChoice);
int getRecordingSquelchOption() const;
void setRecordingFileTimeLimit(int nbSeconds);
int getRecordingFileTimeLimit() const;
#if USE_HAMLIB
int getRigModel();
void setRigModel(int rigModel_in);
int getRigRate();
void setRigRate(int rigRate_in);
std::string getRigPort();
void setRigPort(std::string rigPort_in);
void setRigControlMode(bool cMode);
bool getRigControlMode();
void setRigFollowMode(bool fMode);
bool getRigFollowMode();
void setRigCenterLock(bool cLock);
bool getRigCenterLock();
void setRigFollowModem(bool fMode);
bool getRigFollowModem();
void setRigEnabled(bool enabled);
bool getRigEnabled();
#endif
void setConfigName(std::string configName_in);
std::string getConfigFileName(bool ignoreName=false);
bool save();
bool load();
bool reset();
private:
std::string configName;
std::map<std::string, DeviceConfig *> deviceConfig;
std::atomic_int winX{},winY{},winW{},winH{};
std::atomic_bool winMax{}, showTips{}, modemPropsCollapsed{};
std::atomic_int themeId{};
std::atomic_int fontScale{};
std::atomic_llong snap{};
std::atomic_llong centerFreq{};
std::atomic_int waterfallLinesPerSec{};
std::atomic<float> spectrumAvgSpeed{}, mainSplit{}, visSplit{}, bookmarkSplit{};
std::atomic_int dbOffset{};
std::vector<SDRManualDef> manualDevices;
std::atomic_bool bookmarksVisible{};
std::atomic<PerfModeEnum> perfMode{};
std::string recordingPath;
int recordingSquelchOption = 0;
int recordingFileTimeLimitSeconds = 0;
#if USE_HAMLIB
std::atomic_int rigModel{}, rigRate{};
std::string rigPort;
std::atomic_bool rigEnabled{}, rigFollowMode{}, rigControlMode{}, rigCenterLock{}, rigFollowModem{};
#endif
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,389 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include <wx/frame.h>
#include <wx/panel.h>
#include <wx/splitter.h>
#include <wx/sizer.h>
#include <wx/bitmap.h>
#include <wx/statbmp.h>
#include <wx/tooltip.h>
#include "PrimaryGLContext.h"
#include "ScopeCanvas.h"
#include "SpectrumCanvas.h"
#include "WaterfallCanvas.h"
#include "MeterCanvas.h"
#include "TuningCanvas.h"
#include "ModeSelectorCanvas.h"
#include "GainCanvas.h"
#include "FFTVisualDataThread.h"
#include "SDRDeviceInfo.h"
#include "ModemProperties.h"
//#include "UITestCanvas.h"
#include "FrequencyDialog.h"
#include "BookmarkView.h"
#include "AboutDialog.h"
#include "DemodulatorInstance.h"
#include "DemodulatorThread.h"
#include <map>
#ifdef USE_HAMLIB
class PortSelectorDialog;
#endif
// Define a new frame type
class AppFrame: public wxFrame {
public:
AppFrame();
~AppFrame() override;
void initDeviceParams(SDRDeviceInfo *devInfo_in);
FFTVisualDataThread *getWaterfallDataThread();
WaterfallCanvas *getWaterfallCanvas();
SpectrumCanvas *getSpectrumCanvas();
void notifyUpdateModemProperties();
void setMainWaterfallFFTSize(int fftSize);
void setScopeDeviceName(std::string deviceName);
int OnGlobalKeyDown(wxKeyEvent &event);
int OnGlobalKeyUp(wxKeyEvent &event);
void setWaterfallLinesPerSecond(int lps);
void setSpectrumAvgSpeed(double avg);
FrequencyDialog::FrequencyDialogTarget getFrequencyDialogTarget();
void refreshGainUI();
void setViewState(long long center_freq, int bandwidth);
void setViewState();
long long getViewCenterFreq();
int getViewBandwidth();
bool isUserDemodBusy();
BookmarkView *getBookmarkView();
void disableSave(bool state);
//call this in case the main UI is not
//the origin of device changes / sample rate by operator,
//and must be notified back to update its UI elements
//(ex: SDR Devices dialog changing the configuration)
void notifyDeviceChanged();
#ifdef _WIN32
bool canFocus();
#endif
//set tooltip to window
void setStatusText(wxWindow* window, const std::string& statusText);
void setStatusText(const std::string& statusText, int value);
#ifdef USE_HAMLIB
void setRigControlPort(const std::string& portName);
void dismissRigControlPortDialog();
#endif
private:
/***
* UI Elements
*/
ScopeCanvas *scopeCanvas;
SpectrumCanvas *spectrumCanvas, *demodSpectrumCanvas;
WaterfallCanvas *waterfallCanvas, *demodWaterfallCanvas;
TuningCanvas *demodTuner;
MeterCanvas *demodSignalMeter, *demodGainMeter, *spectrumAvgMeter, *waterfallSpeedMeter;
ModeSelectorCanvas *demodModeSelector, *demodMuteButton, *peakHoldButton, *soloModeButton, *deltaLockButton;
GainCanvas *gainCanvas;
BookmarkView *bookmarkView;
wxSizerItem *gainSizerItem, *gainSpacerItem;
wxSplitterWindow *mainVisSplitter, *mainSplitter, *bookmarkSplitter;
wxBoxSizer *demodTray;
//Use a raw pointer here to prevent a dangling reference
DemodulatorInstance* activeDemodulator;
/***
* Menus
*/
wxMenuBar *menuBar = nullptr;
wxMenu *fileMenu = nullptr;
wxMenu *settingsMenu = nullptr;
wxMenuItem *showTipMenuItem;
wxMenuItem *iqSwapMenuItem = nullptr;
wxMenuItem *agcMenuItem = nullptr;
wxMenu *sampleRateMenu = nullptr;
wxMenu *displayMenu = nullptr;
wxMenuItem *hideBookmarksItem;
wxMenu *recordingMenu = nullptr;
//depending on context, maps the item id to wxMenuItem*,
//OR the submenu item id to its parent wxMenuItem*.
std::map<int, wxMenuItem *> sampleRateMenuItems;
std::map<int, wxMenuItem *> antennaMenuItems;
std::map<int, wxMenuItem *> settingsMenuItems;
std::map<int, wxMenuItem *> performanceMenuItems;
std::map<int, wxMenuItem *> audioSampleRateMenuItems;
std::map<int, wxMenuItem *> recordingMenuItems;
/***
* Waterfall Data Thread
*/
FFTVisualDataThread *waterfallDataThread;
std::thread *t_FFTData;
/***
* Active Settings
*/
bool saveDisabled = false;
std::string currentSessionFile;
std::string currentBookmarkFile;
SoapySDR::ArgInfoList settingArgs;
int settingsIdMax;
std::vector<long> sampleRates;
long manualSampleRate = -1;
SDRDeviceInfo *devInfo = nullptr;
std::atomic_bool deviceChanged;
ModemProperties *modemProps;
std::atomic_bool modemPropertiesUpdated;
std::vector<std::string> antennaNames;
std::string currentTXantennaName;
AboutDialog *aboutDlg = nullptr;
std::string lastToolTip;
#ifdef ENABLE_DIGITAL_LAB
ModeSelectorCanvas *demodModeSelectorAdv;
#endif
/***
* wx Events
*/
void OnMenu(wxCommandEvent& event);
void OnClose(wxCloseEvent& event);
void OnIdle(wxIdleEvent& event);
void OnDoubleClickSash(wxSplitterEvent& event);
void OnUnSplit(wxSplitterEvent& event);
void OnAboutDialogClose(wxCommandEvent& event);
void OnNewWindow(wxCommandEvent& event);
/**
* Session Management
*/
void saveSession(const std::string& fileName);
bool loadSession(const std::string& fileName);
/**
* Keyboard handlers
*/
void gkNudge(const DemodulatorInstancePtr& demod, int snap);
void toggleActiveDemodRecording();
void toggleAllActiveDemodRecording();
/**
* UI init functions
*/
ModeSelectorCanvas *makeModemSelectorPanel(wxWindow *parent, const wxGLAttributes &attribList);
WaterfallCanvas *makeWaterfallCanvas(wxWindow *parent, const wxGLAttributes &attribList);
SpectrumCanvas *makeDemodSpectrumCanvas(wxWindow *parent, const wxGLAttributes &attribList);
MeterCanvas *makeSignalMeter(wxWindow *parent, const wxGLAttributes &attribList);
ModeSelectorCanvas *makeDeltaLockButton(wxWindow *parent, const wxGLAttributes &attribList);
TuningCanvas *makeModemTuner(wxWindow *parent, const wxGLAttributes &attribList);
MeterCanvas *makeModemGainMeter(wxWindow *parent, const wxGLAttributes &attribList);
ModeSelectorCanvas *makeSoloModeButton(wxWindow *parent, const wxGLAttributes &attribList);
ModeSelectorCanvas *makeModemMuteButton(wxWindow *parent, const wxGLAttributes &attribList);
ModeSelectorCanvas *makePeakHoldButton(wxWindow *parent, const wxGLAttributes &attribList);
SpectrumCanvas *makeSpectrumCanvas(wxWindow *parent, const wxGLAttributes &attribList);
MeterCanvas *makeSpectrumAvgMeter(wxWindow *parent, const wxGLAttributes &attribList);
WaterfallCanvas *makeWaterfall(wxWindow *parent, const wxGLAttributes &attribList);
MeterCanvas *makeWaterfallSpeedMeter(wxWindow *parent, const wxGLAttributes &attribList);
ScopeCanvas *makeScopeCanvas(wxPanel *parent, const wxGLAttributes &attribList);
ModeSelectorCanvas *makeModemAdvSelectorPanel(wxPanel *parent, const wxGLAttributes &attribList);
ModemProperties *makeModemProperties(wxPanel *parent);
void initConfigurationSettings();
void initMenuBar();
void initIcon();
wxMenu *makeFileMenu();
wxMenu *makeAudioSampleRateMenu();
wxMenu *makeDisplayMenu();
wxMenu *makeRecordingMenu();
void updateRecordingMenu();
wxString getSettingsLabel(const std::string& settingsName,
const std::string& settingsValue,
const std::string& settingsSuffix = "");
/**
* Menu Action Handlers
*/
//actionXXXX manage menu actions, return true if the event has been
//treated.
bool actionOnMenuAbout(wxCommandEvent& event);
bool actionOnMenuReset(wxCommandEvent& event);
bool actionOnMenuSettings(wxCommandEvent& event);
bool actionOnMenuAGC(wxCommandEvent& event);
bool actionOnMenuSampleRate(wxCommandEvent& event);
bool actionOnMenuAudioSampleRate(wxCommandEvent& event);
bool actionOnMenuDisplay(wxCommandEvent& event);
bool actionOnMenuLoadSave(wxCommandEvent& event);
bool actionOnMenuRecording(wxCommandEvent& event);
bool actionOnMenuRig(wxCommandEvent& event);
bool actionOnMenuSDRStartStop(wxCommandEvent &event);
bool actionOnMenuPerformance(wxCommandEvent &event);
bool actionOnMenuTips(wxCommandEvent &event);
bool actionOnMenuIQSwap(wxCommandEvent &event);
bool actionOnMenuFreqOffset(wxCommandEvent &event);
bool actionOnMenuDBOffset(wxCommandEvent &event);
bool actionOnMenuSDRDevices(wxCommandEvent &event);
bool actionOnMenuSetPPM(wxCommandEvent &event);
bool actionOnMenuClose(wxCommandEvent &event);
/**
* UI Activity Handlers
*/
void handleUpdateDeviceParams();
void handleTXAntennaChange();
void handleCurrentModem();
void handleModeSelector();
void handleGainMeter();
void handleDemodWaterfallSpectrum();
void handleSpectrumWaterfall();
void handleMuteButton();
void handleScopeProcessor();
void handleScopeSpectrumProcessors();
void handleModemProperties();
void handlePeakHold();
/**
* Hamlib/Rig specific
*/
#ifdef USE_HAMLIB
wxMenu *rigMenu;
wxMenuItem *rigEnableMenuItem;
wxMenuItem *rigPortMenuItem;
wxMenuItem *rigControlMenuItem;
wxMenuItem *rigFollowMenuItem;
wxMenuItem *rigCenterLockMenuItem;
wxMenuItem *rigFollowModemMenuItem;
std::map<int, wxMenuItem *> rigSerialMenuItems;
std::map<int, wxMenuItem *> rigModelMenuItems;
wxMenu *rigModelMenu;
int rigModel;
int rigSerialRate;
long long rigSDRIF;
std::vector<int> rigSerialRates;
std::string rigPort;
int numRigs;
PortSelectorDialog *rigPortDialog;
void enableRig();
void disableRig();
wxMenu *makeRigMenu();
void handleRigMenu();
#endif
wxDECLARE_EVENT_TABLE();
};
#define wxID_RT_AUDIO_DEVICE 1000
#define wxID_SET_FREQ_OFFSET 2001
#define wxID_RESET 2002
#define wxID_SET_PPM 2003
#define wxID_SET_TIPS 2004
#define wxID_SET_IQSWAP 2005
#define wxID_SDR_DEVICES 2008
#define wxID_AGC_CONTROL 2009
#define wxID_SDR_START_STOP 2010
#define wxID_SET_DB_OFFSET 2012
#define wxID_ABOUT_CUBICSDR 2013
#define wxID_OPEN_BOOKMARKS 2020
#define wxID_SAVE_BOOKMARKS 2021
#define wxID_SAVEAS_BOOKMARKS 2022
#define wxID_RESET_BOOKMARKS 2023
#define wxID_MAIN_SPLITTER 2050
#define wxID_VIS_SPLITTER 2051
#define wxID_BM_SPLITTER 2052
#define wxID_THEME_DEFAULT 2070
#define wxID_THEME_DEFAULT_JET 2071
#define wxID_THEME_SHARP 2072
#define wxID_THEME_BW 2073
#define wxID_THEME_RAD 2074
#define wxID_THEME_TOUCH 2075
#define wxID_THEME_HD 2076
#define wxID_THEME_RADAR 2077
#define wxID_DISPLAY_BOOKMARKS 2100
#define wxID_BANDWIDTH_BASE 2150
#define wxID_BANDWIDTH_MANUAL_DIALOG 2199
#define wxID_BANDWIDTH_MANUAL 2200
#define wxID_DISPLAY_BASE 2250
#define wxID_SETTINGS_BASE 2300
#define wxID_ANTENNA_CURRENT 2350
#define wxID_ANTENNA_CURRENT_TX 2501
#define wxID_ANTENNAS_BASE 2352
#define wxID_PERF_CURRENT 2400
#define wxID_PERF_BASE 2401
#define wxID_DEVICE_ID 3500
#define wxID_RECORDING_PATH 8500
#define wxID_RECORDING_SQUELCH_BASE 8501
#define wxID_RECORDING_SQUELCH_SILENCE 8502
#define wxID_RECORDING_SQUELCH_SKIP 8503
#define wxID_RECORDING_SQUELCH_ALWAYS 8504
#define wxID_RECORDING_FILE_TIME_LIMIT 8505
#define wxID_AUDIO_BANDWIDTH_BASE 9000
#define wxID_AUDIO_DEVICE_MULTIPLIER 50
#ifdef USE_HAMLIB
#define wxID_RIG_TOGGLE 11900
#define wxID_RIG_PORT 11901
#define wxID_RIG_SDR_IF 11902
#define wxID_RIG_CONTROL 11903
#define wxID_RIG_FOLLOW 11904
#define wxID_RIG_CENTERLOCK 11905
#define wxID_RIG_FOLLOW_MODEM 11906
#define wxID_RIG_SERIAL_BASE 11950
#define wxID_RIG_MODEL_BASE 12000
#endif

View File

@ -0,0 +1,677 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "BookmarkMgr.h"
#include "CubicSDR.h"
#include "DataTree.h"
#define BOOKMARK_RECENTS_MAX 25
BookmarkEntry::~BookmarkEntry() {
delete node;
}
BookmarkMgr::BookmarkMgr() {
rangesSorted = false;
}
//
void BookmarkMgr::saveToFile(const std::string& bookmarkFn, bool backup, bool useFullpath) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
DataTree s("cubicsdr_bookmarks");
DataNode *header = s.rootNode()->newChild("header");
header->newChild("version")->element()->set(wxString(CUBICSDR_VERSION).ToStdWstring());
DataNode *branches = s.rootNode()->newChild("branches");
*branches->newChild("active") = wxGetApp().getAppFrame()->getBookmarkView()->getExpandState("active")?1:0;
*branches->newChild("range") = wxGetApp().getAppFrame()->getBookmarkView()->getExpandState("range")?1:0;
*branches->newChild("bookmark") = wxGetApp().getAppFrame()->getBookmarkView()->getExpandState("bookmark")?1:0;
*branches->newChild("recent") = wxGetApp().getAppFrame()->getBookmarkView()->getExpandState("recent")?1:0;
DataNode *view_ranges = s.rootNode()->newChild("ranges");
for (const auto& re_i : ranges) {
DataNode *range = view_ranges->newChild("range");
*range->newChild("label") = re_i->label;
*range->newChild("freq") = re_i->freq;
*range->newChild("start") = re_i->startFreq;
*range->newChild("end") = re_i->endFreq;
}
DataNode *modems = s.rootNode()->newChild("modems");
for (auto &bmd_i : bmData) {
DataNode *group = modems->newChild("group");
*group->newChild("@name") = bmd_i.first;
*group->newChild("@expanded") = (getExpandState(bmd_i.first)?std::string("true"):std::string("false"));
for (auto &bm_i : bmd_i.second ) {
//if a matching demodulator exists, use its data instead to be be saved, because output_device could have been
//modified by the user. So, save that "live" version instead.
auto matchingDemod = wxGetApp().getDemodMgr().getLastDemodulatorWith(bm_i->type,
bm_i->label,
bm_i->frequency,
bm_i->bandwidth);
if (matchingDemod != nullptr) {
wxGetApp().getDemodMgr().saveInstance(group->newChild("modem"), matchingDemod);
}
else {
group->newChildCloneFrom("modem", bm_i->node);
}
}
}
DataNode *recent_modems = s.rootNode()->newChild("recent_modems");
for (const auto& demod : wxGetApp().getDemodMgr().getDemodulators()) {
wxGetApp().getDemodMgr().saveInstance(recent_modems->newChild("modem"),demod);
}
for (auto &r_i : this->recents) {
recent_modems->newChildCloneFrom("modem", r_i->node);
}
wxFileName saveFile;
wxFileName saveFileBackup;
if (useFullpath) {
saveFile.Assign(bookmarkFn);
saveFileBackup.Assign(bookmarkFn + ".backup");
}
else {
saveFile.Assign(wxGetApp().getConfig()->getConfigDir(), bookmarkFn);
saveFileBackup.Assign(wxGetApp().getConfig()->getConfigDir(), bookmarkFn + ".backup");
}
if (saveFile.IsDirWritable()) {
// Hopefully leave at least a readable backup in case of failure..
if (backup && saveFile.FileExists() && (!saveFileBackup.FileExists() || saveFileBackup.IsFileWritable())) {
wxCopyFile(saveFile.GetFullPath(wxPATH_NATIVE).ToStdString(), saveFileBackup.GetFullPath(wxPATH_NATIVE).ToStdString());
}
s.SaveToFileXML(saveFile.GetFullPath(wxPATH_NATIVE).ToStdString());
}
}
bool BookmarkMgr::loadFromFile(const std::string& bookmarkFn, bool backup, bool useFullpath) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
wxFileName loadFile;
wxFileName failFile;
wxFileName lastLoaded;
wxFileName backupFile;
if (useFullpath) {
loadFile.Assign(bookmarkFn);
failFile.Assign(bookmarkFn + ".failedload");
lastLoaded.Assign(bookmarkFn + ".lastloaded");
backupFile.Assign(bookmarkFn + ".backup");
}
else {
loadFile.Assign(wxGetApp().getConfig()->getConfigDir(), bookmarkFn);
failFile.Assign(wxGetApp().getConfig()->getConfigDir(), bookmarkFn + ".failedload");
lastLoaded.Assign(wxGetApp().getConfig()->getConfigDir(), bookmarkFn + ".lastloaded");
backupFile.Assign(wxGetApp().getConfig()->getConfigDir(), bookmarkFn + ".backup");
}
DataTree s;
bool loadStatusOk = true;
// File exists but is not readable
if (loadFile.FileExists() && !loadFile.IsFileReadable()) {
return false;
}
// New instance of bookmark savefiles
if (backup && !loadFile.FileExists() && !lastLoaded.FileExists() && !backupFile.FileExists()) {
loadDefaultRanges();
return true;
}
// Attempt to load file
if (!s.LoadFromFileXML(loadFile.GetFullPath(wxPATH_NATIVE).ToStdString())) {
return false;
}
//Check if it is a bookmark file, read the root node.
if (s.rootNode()->getName() != "cubicsdr_bookmarks") {
return false;
}
// Clear any active data
bmData.clear();
recents.clear();
ranges.clear();
bmDataSorted.clear();
if (s.rootNode()->hasAnother("branches")) {
DataNode *branches = s.rootNode()->getNext("branches");
int bActive = 1, bRange = 0, bBookmark = 1, bRecent = 1;
if (branches->hasAnother("active")) branches->getNext("active")->element()->get(bActive);
if (branches->hasAnother("range")) branches->getNext("range")->element()->get(bRange);
if (branches->hasAnother("bookmark")) branches->getNext("bookmark")->element()->get(bBookmark);
if (branches->hasAnother("recent")) branches->getNext("recent")->element()->get(bRecent);
wxGetApp().getAppFrame()->getBookmarkView()->setExpandState("active", bActive != 0);
wxGetApp().getAppFrame()->getBookmarkView()->setExpandState("range", bRange != 0);
wxGetApp().getAppFrame()->getBookmarkView()->setExpandState("bookmark", bBookmark != 0);
wxGetApp().getAppFrame()->getBookmarkView()->setExpandState("recent", bRecent != 0);
}
if (s.rootNode()->hasAnother("ranges")) {
DataNode *view_ranges = s.rootNode()->getNext("ranges");
while (view_ranges->hasAnother("range")) {
DataNode *range = view_ranges->getNext("range");
BookmarkRangeEntryPtr re(new BookmarkRangeEntry);
if (range->hasAnother("label")) range->getNext("label")->element()->get(re->label);
if (range->hasAnother("freq")) range->getNext("freq")->element()->get(re->freq);
if (range->hasAnother("start")) range->getNext("start")->element()->get(re->startFreq);
if (range->hasAnother("end")) range->getNext("end")->element()->get(re->endFreq);
addRange(re);
}
}
if (s.rootNode()->hasAnother("modems")) {
DataNode *modems = s.rootNode()->getNext("modems");
while (modems->hasAnother("group")) {
DataNode *group = modems->getNext("group");
std::string groupExpandState = "true";
std::string groupName = "Unnamed";
if (group->hasAnother("@name")) {
groupName = group->getNext("@name")->element()->toString();
}
if (group->hasAnother("@expanded")) {
groupExpandState = group->getNext("@expanded")->element()->toString();
}
setExpandState(groupName, (groupExpandState == "true"));
while (group->hasAnother("modem")) {
DataNode *modem = group->getNext("modem");
BookmarkEntryPtr be = nodeToBookmark(modem);
if (be) {
addBookmark(groupName, be);
} else {
std::cout << "error loading bookmarked modem.." << std::endl;
loadStatusOk = false;
}
}
}
}
if (s.rootNode()->hasAnother("recent_modems")) {
DataNode *recent_modems = s.rootNode()->getNext("recent_modems");
while (recent_modems->hasAnother("modem")) {
DataNode *modem = recent_modems->getNext("modem");
BookmarkEntryPtr be = nodeToBookmark(modem);
if (be) {
addRecent(be);
} else {
std::cout << "error loading recent modem.." << std::endl;
loadStatusOk = false;
}
}
}
if (backup) {
if (loadStatusOk) { // Loaded OK; keep a copy
if (loadFile.IsDirWritable()) {
if (loadFile.FileExists() && (!lastLoaded.FileExists() || lastLoaded.IsFileWritable())) {
wxCopyFile(loadFile.GetFullPath(wxPATH_NATIVE).ToStdString(), lastLoaded.GetFullPath(wxPATH_NATIVE).ToStdString());
}
}
} else {
if (loadFile.IsDirWritable()) { // Load failed; keep a copy of the failed bookmark file for analysis?
if (loadFile.FileExists() && (!failFile.FileExists() || failFile.IsFileWritable())) {
wxCopyFile(loadFile.GetFullPath(wxPATH_NATIVE).ToStdString(), failFile.GetFullPath(wxPATH_NATIVE).ToStdString());
}
}
}
}
return loadStatusOk;
}
void BookmarkMgr::loadDefaultRanges() {
addRange(std::make_shared<BookmarkRangeEntry>(L"2200 Meters (135.7-137.8 kHz)", 136750, 135700, 137800));
addRange(std::make_shared<BookmarkRangeEntry>(L"630 Meters (472-479 kHz)", 475500, 472000, 479000));
addRange(std::make_shared<BookmarkRangeEntry>(L"160 Meters (1.8-2 MHz)", 1900000, 1800000, 2000000));
addRange(std::make_shared<BookmarkRangeEntry>(L"80 Meters (3.5-4.0 MHz)", 3750000, 3500000, 4000000));
addRange(std::make_shared<BookmarkRangeEntry>(L"60 Meters (5.332-5.405Mhz)", 5368500, 5332000, 5405000));
addRange(std::make_shared<BookmarkRangeEntry>(L"40 Meters (7.0-7.3 MHz)", 7150000, 7000000, 7300000));
addRange(std::make_shared<BookmarkRangeEntry>(L"30 Meters (10.1-10.15 MHz)", 10125000, 10100000, 10150000));
addRange(std::make_shared<BookmarkRangeEntry>(L"20 Meters (14.0-14.35 MHz)", 14175000, 14000000, 14350000));
addRange(std::make_shared<BookmarkRangeEntry>(L"17 Meters (17.044-19.092 MHz)", 18068180, 17044180, 19092180));
addRange(std::make_shared<BookmarkRangeEntry>(L"15 Meters (21-21.45 MHz)", 21225000, 21000000, 21450000));
addRange(std::make_shared<BookmarkRangeEntry>(L"12 Meters (24.89-24.99 MHz)", 24940000, 24890000, 24990000));
addRange(std::make_shared<BookmarkRangeEntry>(L"10 Meters (28-29.7 MHz)", 28850000, 28000000, 29700000));
addRange(std::make_shared<BookmarkRangeEntry>(L"6 Meters (50-54 MHz)", 52000000, 50000000, 54000000));
addRange(std::make_shared<BookmarkRangeEntry>(L"4 Meters (70-70.5 MHz)", 70250000, 70000000, 70500000));
addRange(std::make_shared<BookmarkRangeEntry>(L"2 Meters (144-148 MHz)", 146000000, 144000000, 148000000));
addRange(std::make_shared<BookmarkRangeEntry>(L"1.25 Meters (219-225 MHz)", 222000000, 219000000, 225000000));
addRange(std::make_shared<BookmarkRangeEntry>(L"70 cm (420-450 MHz)", 435000000, 420000000, 450000000));
addRange(std::make_shared<BookmarkRangeEntry>(L"33 cm (902-928 MHz)", 915000000, 902000000, 928000000));
addRange(std::make_shared<BookmarkRangeEntry>(L"23 cm (1240-1300 MHz)", 1270000000, 1240000000, 1300000000));
addRange(std::make_shared<BookmarkRangeEntry>(L"13 cm lower (2300-2310 MHz)", 2305000000, 2300000000, 2310000000));
addRange(std::make_shared<BookmarkRangeEntry>(L"13 cm upper (2390-2450 MHz)", 2420000000, 2390000000, 2450000000));
}
void BookmarkMgr::resetBookmarks() {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
// Clear any active data
bmData.clear();
recents.clear();
ranges.clear();
bmDataSorted.clear();
loadDefaultRanges();
}
bool BookmarkMgr::hasLastLoad(const std::string& bookmarkFn) {
wxFileName lastLoaded(wxGetApp().getConfig()->getConfigDir(), bookmarkFn + ".lastloaded");
return lastLoaded.FileExists() && lastLoaded.IsFileReadable();
}
bool BookmarkMgr::hasBackup(const std::string& bookmarkFn) {
wxFileName backupFile(wxGetApp().getConfig()->getConfigDir(), bookmarkFn + ".backup");
return backupFile.FileExists() && backupFile.IsFileReadable();
}
void BookmarkMgr::addBookmark(const std::string& group, const DemodulatorInstancePtr& demod) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
//Create a BookmarkEntry from demod data, saving its
//characteristics in be->node.
BookmarkEntryPtr be = demodToBookmarkEntry(demod);
bmData[group].push_back(be);
bmDataSorted[group] = false;
}
void BookmarkMgr::addBookmark(const std::string& group, const BookmarkEntryPtr& be) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
bmData[group].push_back(be);
bmDataSorted[group] = false;
}
void BookmarkMgr::removeBookmark(const std::string& group, const BookmarkEntryPtr& be) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
if (bmData.find(group) == bmData.end()) {
return;
}
auto i = std::find(bmData[group].begin(), bmData[group].end(), be);
if (i != bmData[group].end()) {
bmData[group].erase(i);
}
}
void BookmarkMgr::removeBookmark(const BookmarkEntryPtr& be) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
for (auto &bmd_i : bmData) {
auto i = std::find(bmd_i.second.begin(), bmd_i.second.end(), be);
if (i != bmd_i.second.end()) {
bmd_i.second.erase(i);
}
}
}
void BookmarkMgr::moveBookmark(const BookmarkEntryPtr& be, const std::string& group) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
for (auto &bmd_i : bmData) {
auto i = std::find(bmd_i.second.begin(), bmd_i.second.end(), be);
if (i != bmd_i.second.end()) {
if (bmd_i.first == group) {
return;
}
bmData[group].push_back(*i);
bmd_i.second.erase(i);
bmDataSorted[group] = false;
bmDataSorted[bmd_i.first] = false;
return;
}
}
}
void BookmarkMgr::addGroup(const std::string& group) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
if (bmData.find(group) == bmData.end()) {
BookmarkList dummy = bmData[group];
}
}
void BookmarkMgr::removeGroup(const std::string& group) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
auto i = bmData.find(group);
if (i != bmData.end()) {
bmData.erase(group);
}
}
void BookmarkMgr::renameGroup(const std::string& group, const std::string& ngroup) {
if (group == ngroup) {
return;
}
std::lock_guard < std::recursive_mutex > lock(busy_lock);
auto i = bmData.find(group);
auto it = bmData.find(ngroup);
if (i != bmData.end() && it != bmData.end()) {
for (const auto& ii : bmData[group]) {
bmData[ngroup].push_back(ii);
}
bmData.erase(group);
} else if (i != bmData.end()) {
bmData[ngroup] = bmData[group];
bmData.erase(group);
}
}
BookmarkList BookmarkMgr::getBookmarks(const std::string& group) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
if (bmData.find(group) == bmData.end()) {
return BookmarkList();
}
if (!bmDataSorted[group]) {
std::sort(bmData[group].begin(), bmData[group].end(), [](const BookmarkEntryPtr a, const BookmarkEntryPtr b) -> bool { return a->frequency < b->frequency;});
bmDataSorted[group] = true;
}
return bmData[group];
}
void BookmarkMgr::getGroups(BookmarkNames &arr) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
for (auto & i : bmData) {
arr.push_back(i.first);
}
}
void BookmarkMgr::getGroups(wxArrayString &arr) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
for (auto & i : bmData) {
arr.push_back(i.first);
}
}
void BookmarkMgr::setExpandState(const std::string& groupName, bool state) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
expandState[groupName] = state;
}
bool BookmarkMgr::getExpandState(const std::string& groupName) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
if (expandState.find(groupName) == expandState.end()) {
return true;
}
return expandState[groupName];
}
void BookmarkMgr::updateActiveList() {
if (wxGetApp().isShuttingDown()) {
return;
}
BookmarkView *bmv = wxGetApp().getAppFrame()->getBookmarkView();
if (bmv) {
bmv->updateActiveList();
}
}
void BookmarkMgr::updateBookmarks() {
BookmarkView *bmv = wxGetApp().getAppFrame()->getBookmarkView();
if (bmv) {
bmv->updateBookmarks();
}
}
void BookmarkMgr::updateBookmarks(const std::string& group) {
BookmarkView *bmv = wxGetApp().getAppFrame()->getBookmarkView();
if (bmv) {
bmv->updateBookmarks(group);
}
}
void BookmarkMgr::addRecent(const DemodulatorInstancePtr& demod) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
recents.push_back(demodToBookmarkEntry(demod));
trimRecents();
}
void BookmarkMgr::addRecent(const BookmarkEntryPtr& be) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
recents.push_back(be);
trimRecents();
}
void BookmarkMgr::removeRecent(const BookmarkEntryPtr& be) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
auto bm_i = std::find(recents.begin(),recents.end(), be);
if (bm_i != recents.end()) {
recents.erase(bm_i);
}
}
BookmarkList BookmarkMgr::getRecents() {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
//return a copy
return recents;
}
void BookmarkMgr::clearRecents() {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
recents.clear();
}
void BookmarkMgr::trimRecents() {
if (recents.size() > BOOKMARK_RECENTS_MAX) {
recents.erase(recents.begin(), recents.begin()+1);
}
}
void BookmarkMgr::addRange(const BookmarkRangeEntryPtr& re) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
ranges.push_back(re);
rangesSorted = false;
}
void BookmarkMgr::removeRange(const BookmarkRangeEntryPtr& re) {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
auto re_i = std::find(ranges.begin(), ranges.end(), re);
if (re_i != ranges.end()) {
ranges.erase(re_i);
}
}
BookmarkRangeList BookmarkMgr::getRanges() {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
if (!rangesSorted) {
std::sort(ranges.begin(), ranges.end(), [](const BookmarkRangeEntryPtr a, const BookmarkRangeEntryPtr b) -> bool { return a->freq < b->freq;});
rangesSorted = true;
}
return ranges;
}
void BookmarkMgr::clearRanges() {
std::lock_guard < std::recursive_mutex > lock(busy_lock);
ranges.clear();
}
BookmarkEntryPtr BookmarkMgr::demodToBookmarkEntry(const DemodulatorInstancePtr& demod) {
BookmarkEntryPtr be(new BookmarkEntry);
be->bandwidth = demod->getBandwidth();
be->type = demod->getDemodulatorType();
be->label = demod->getDemodulatorUserLabel();
be->frequency = demod->getFrequency();
//fine to do so here, so long nobody overrides be->node, DataNode will be
//deleted at last BookmarkEntryPtr be ref.
be->node = new DataNode;
wxGetApp().getDemodMgr().saveInstance(be->node, demod);
return be;
}
std::wstring BookmarkMgr::getSafeWstringValue(DataNode* node, const std::string& childNodeName) {
std::wstring decodedWString;
if (node != nullptr) {
DataNode* childNode = node->getNext(childNodeName.c_str());
//1) decode as encoded wstring:
try {
childNode->element()->get(decodedWString);
} catch (const DataTypeMismatchException &) {
//2) wstring decode fail, try simple std::string
std::string decodedStdString;
try {
childNode->element()->get(decodedStdString);
//use wxString for a clean conversion to a wstring:
decodedWString = wxString(decodedStdString).ToStdWstring();
} catch (const DataTypeMismatchException &) {
//nothing works, return an empty string.
decodedWString = L"";
}
}
}
return decodedWString;
}
BookmarkEntryPtr BookmarkMgr::nodeToBookmark(DataNode *node) {
if (!node->hasAnother("frequency") || !node->hasAnother("type") || !node->hasAnother("bandwidth")) {
return nullptr;
}
BookmarkEntryPtr be(new BookmarkEntry());
node->getNext("frequency")->element()->get(be->frequency);
node->getNext("type")->element()->get(be->type);
node->getNext("bandwidth")->element()->get(be->bandwidth);
if (node->hasAnother("user_label")) {
be->label = BookmarkMgr::getSafeWstringValue( node, "user_label");
}
node->rewindAll();
//fine to do so here, so long nobody overrides be->node, DataNode will be
//deleted at last BookmarkEntryPtr be ref.
//copy data from *node.
be->node = new DataNode("node",*node);
return be;
}
std::wstring BookmarkMgr::getBookmarkEntryDisplayName(const BookmarkEntryPtr& bmEnt) {
std::wstring dispName = bmEnt->label;
if (dispName.empty()) {
std::string freqStr = frequencyToStr(bmEnt->frequency) + " " + bmEnt->type;
dispName = wxString(freqStr).ToStdWstring();
}
return dispName;
}
std::wstring BookmarkMgr::getActiveDisplayName(const DemodulatorInstancePtr& demod) {
std::wstring activeName = demod->getDemodulatorUserLabel();
if (activeName.empty()) {
std::string wstr = frequencyToStr(demod->getFrequency()) + " " + demod->getDemodulatorType();
activeName = wxString(wstr).ToStdWstring();
}
return activeName;
}
void BookmarkMgr::removeActive(const DemodulatorInstancePtr& demod) {
if (demod == nullptr) {
return;
}
//Delete demodulator
wxGetApp().getDemodMgr().setActiveDemodulator(nullptr, true);
wxGetApp().getDemodMgr().setActiveDemodulator(nullptr, false);
wxGetApp().removeDemodulator(demod);
wxGetApp().getDemodMgr().deleteThread(demod);
}

View File

@ -0,0 +1,137 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include <wx/arrstr.h>
#include <vector>
#include <set>
#include <memory>
#include "DemodulatorInstance.h"
class DataNode;
class BookmarkEntry {
public:
std::string type;
//maps on the Demod user label.
std::wstring label;
long long frequency;
int bandwidth;
DataNode *node;
virtual ~BookmarkEntry();
};
class BookmarkRangeEntry {
public:
BookmarkRangeEntry() : freq(0), startFreq(0), endFreq(0) {
}
BookmarkRangeEntry(std::wstring label, long long freq, long long startFreq, long long endFreq) : label(label), freq(freq), startFreq(startFreq), endFreq(endFreq) {
}
std::wstring label;
long long freq;
long long startFreq;
long long endFreq;
};
typedef std::shared_ptr<BookmarkEntry> BookmarkEntryPtr;
typedef std::shared_ptr<BookmarkRangeEntry> BookmarkRangeEntryPtr;
typedef std::vector<BookmarkEntryPtr> BookmarkList;
typedef std::vector<BookmarkRangeEntryPtr> BookmarkRangeList;
typedef std::map<std::string, BookmarkList > BookmarkMap;
typedef std::map<std::string, bool > BookmarkMapSorted;
typedef std::vector<std::string> BookmarkNames;
typedef std::map<std::string, bool> BookmarkExpandState;
class BookmarkMgr {
public:
BookmarkMgr();
//if useFullpath = false, use the application config dir.
//else assume bookmarkFn is a full path and use it for location.
void saveToFile(const std::string& bookmarkFn, bool backup = true, bool useFullpath = false);
bool loadFromFile(const std::string& bookmarkFn, bool backup = true, bool useFullpath = false);
void resetBookmarks();
bool hasLastLoad(const std::string& bookmarkFn);
bool hasBackup(const std::string& bookmarkFn);
void addBookmark(const std::string& group, const DemodulatorInstancePtr& demod);
void addBookmark(const std::string& group, const BookmarkEntryPtr& be);
void removeBookmark(const std::string& group, const BookmarkEntryPtr& be);
void removeBookmark(const BookmarkEntryPtr& be);
void moveBookmark(const BookmarkEntryPtr& be, const std::string& group);
void addGroup(const std::string& group);
void removeGroup(const std::string& group);
void renameGroup(const std::string& group, const std::string& ngroup);
//return an independent copy on purpose
BookmarkList getBookmarks(const std::string& group);
void getGroups(BookmarkNames &arr);
void getGroups(wxArrayString &arr);
void setExpandState(const std::string& groupName, bool state);
bool getExpandState(const std::string& groupName);
void updateActiveList();
void updateBookmarks();
void updateBookmarks(const std::string& group);
void addRecent(const DemodulatorInstancePtr& demod);
void addRecent(const BookmarkEntryPtr& be);
void removeRecent(const BookmarkEntryPtr& be);
//return an independent copy on purpose
BookmarkList getRecents();
void clearRecents();
void removeActive(const DemodulatorInstancePtr& demod);
void addRange(const BookmarkRangeEntryPtr& re);
void removeRange(const BookmarkRangeEntryPtr& re);
//return an independent copy on purpose
BookmarkRangeList getRanges();
void clearRanges();
static std::wstring getBookmarkEntryDisplayName(const BookmarkEntryPtr& bmEnt);
static std::wstring getActiveDisplayName(const DemodulatorInstancePtr& demod);
protected:
void trimRecents();
void loadDefaultRanges();
//utility method that attempts to decode the childNodeName as std::wstring, else as std::string, else
//return an empty string.
static std::wstring getSafeWstringValue(DataNode* node, const std::string& childNodeName);
BookmarkEntryPtr demodToBookmarkEntry(const DemodulatorInstancePtr& demod);
BookmarkEntryPtr nodeToBookmark(DataNode *node);
BookmarkMap bmData;
BookmarkMapSorted bmDataSorted;
BookmarkList recents;
BookmarkRangeList ranges;
bool rangesSorted;
std::recursive_mutex busy_lock;
BookmarkExpandState expandState;
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,270 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
//WX_GL_CORE_PROFILE 1
//WX_GL_MAJOR_VERSION 3
//WX_GL_MINOR_VERSION 2
#include <thread>
#include "GLExt.h"
#include "PrimaryGLContext.h"
#include "ThreadBlockingQueue.h"
#include "SoapySDRThread.h"
#include "SDREnumerator.h"
#include "SDRPostThread.h"
#include "AudioThread.h"
#include "DemodulatorMgr.h"
#include "AppConfig.h"
#include "AppFrame.h"
#include "FrequencyDialog.h"
#include "DemodLabelDialog.h"
#include "BookmarkMgr.h"
#include "SessionMgr.h"
#include "ScopeVisualProcessor.h"
#include "SpectrumVisualProcessor.h"
#include "SpectrumVisualDataThread.h"
#include "SDRDevices.h"
#include "Modem.h"
#include "ModemFM.h"
#include "ModemNBFM.h"
#include "ModemFMStereo.h"
#include "ModemCW.h"
#include "ModemAM.h"
#include "ModemUSB.h"
#include "ModemLSB.h"
#include "ModemDSB.h"
#include "ModemIQ.h"
#ifdef ENABLE_DIGITAL_LAB
#include "ModemAPSK.h"
#include "ModemASK.h"
#include "ModemBPSK.h"
#include "ModemDPSK.h"
#include "ModemFSK.h"
#include "ModemGMSK.h"
#include "ModemOOK.h"
#include "ModemPSK.h"
#include "ModemQAM.h"
#include "ModemQPSK.h"
#include "ModemSQAM.h"
#include "ModemST.h"
#endif
#ifdef USE_HAMLIB
class RigThread;
#endif
#include <wx/cmdline.h>
#define NUM_DEMODULATORS 1
std::string& filterChars(std::string& s, const std::string& allowed);
std::string frequencyToStr(long long freq);
long long strToFrequency(std::string freqStr);
class CubicSDR: public wxApp {
public:
CubicSDR();
PrimaryGLContext &GetContext(wxGLCanvas *canvas);
wxGLContextAttrs* GetContextAttributes();
bool OnInit() override;
int OnExit() override;
void OnInitCmdLine(wxCmdLineParser& parser) override;
bool OnCmdLineParsed(wxCmdLineParser& parser) override;
void deviceSelector();
void sdrThreadNotify(SDRThread::SDRThreadState state, const std::string& message);
void sdrEnumThreadNotify(SDREnumerator::SDREnumState state, std::string message);
void setFrequency(long long freq);
long long getFrequency();
void lockFrequency(long long freq);
bool isFrequencyLocked();
void unlockFrequency();
void setOffset(long long ofs);
long long getOffset();
void setAntennaName(const std::string& name);
const std::string& getAntennaName();
void setChannelizerType(SDRPostThreadChannelizerType chType);
SDRPostThreadChannelizerType getChannelizerType();
void setSampleRate(long long rate_in);
long long getSampleRate();
std::vector<SDRDeviceInfo *> *getDevices();
void setDevice(SDRDeviceInfo *dev, int waitMsForTermination);
void stopDevice(bool store, int waitMsForTermination);
SDRDeviceInfo * getDevice();
ScopeVisualProcessor *getScopeProcessor();
SpectrumVisualProcessor *getSpectrumProcessor();
SpectrumVisualProcessor *getDemodSpectrumProcessor();
DemodulatorThreadOutputQueuePtr getAudioVisualQueue();
DemodulatorThreadInputQueuePtr getIQVisualQueue();
DemodulatorThreadInputQueuePtr getWaterfallVisualQueue();
DemodulatorMgr &getDemodMgr();
BookmarkMgr &getBookmarkMgr();
SessionMgr &getSessionMgr();
SDRPostThread *getSDRPostThread();
SDRThread *getSDRThread();
void notifyDemodulatorsChanged();
void removeDemodulator(const DemodulatorInstancePtr& demod);
void setFrequencySnap(int snap_in);
int getFrequencySnap();
AppConfig *getConfig();
void saveConfig();
void setPPM(int ppm_in);
int getPPM();
void showFrequencyInput(FrequencyDialog::FrequencyDialogTarget targetMode = FrequencyDialog::FDIALOG_TARGET_DEFAULT, const wxString& initString = "");
void showLabelInput();
AppFrame *getAppFrame();
bool areDevicesReady();
bool areDevicesEnumerating();
bool areModulesMissing();
std::string getNotification();
void notifyMainUIOfDeviceChange(bool forceRefreshOfGains = false);
void addRemote(const std::string& remoteAddr);
void removeRemote(const std::string& remoteAddr);
void setDeviceSelectorClosed();
void reEnumerateDevices();
bool isDeviceSelectorOpen();
void closeDeviceSelector();
void setAGCMode(bool mode);
bool getAGCMode();
void setGain(const std::string& name, float gain_in);
float getGain(const std::string& name);
void setStreamArgs(SoapySDR::Kwargs streamArgs_in);
void setDeviceArgs(SoapySDR::Kwargs settingArgs_in);
bool getUseLocalMod();
std::string getModulePath();
void setActiveGainEntry(std::string gainName);
std::string getActiveGainEntry();
void setSoloMode(bool solo);
bool getSoloMode();
bool isShuttingDown();
#ifdef USE_HAMLIB
RigThread *getRigThread();
void initRig(int rigModel, std::string rigPort, int rigSerialRate);
void stopRig();
bool rigIsActive();
#endif
private:
int FilterEvent(wxEvent& event) override;
AppFrame *appframe = nullptr;
AppConfig config;
PrimaryGLContext *m_glContext = nullptr;
wxGLContextAttrs *m_glContextAttributes = nullptr;
std::vector<SDRDeviceInfo *> *devs = nullptr;
DemodulatorMgr demodMgr;
BookmarkMgr bookmarkMgr;
SessionMgr sessionMgr;
std::atomic_llong frequency;
std::atomic_llong offset;
std::atomic_int ppm, snap;
std::atomic_llong sampleRate;
std::string antennaName;
std::atomic_bool agcMode;
std::atomic_bool shuttingDown;
SDRThread *sdrThread = nullptr;
SDREnumerator *sdrEnum = nullptr;
SDRPostThread *sdrPostThread = nullptr;
SpectrumVisualDataThread *spectrumVisualThread = nullptr;
SpectrumVisualDataThread *demodVisualThread = nullptr;
SDRThreadIQDataQueuePtr pipeSDRIQData;
DemodulatorThreadInputQueuePtr pipeIQVisualData;
DemodulatorThreadOutputQueuePtr pipeAudioVisualData;
DemodulatorThreadInputQueuePtr pipeDemodIQVisualData;
DemodulatorThreadInputQueuePtr pipeWaterfallIQVisualData;
DemodulatorThreadInputQueuePtr pipeActiveDemodIQVisualData;
ScopeVisualProcessor scopeProcessor;
SDRDevicesDialog *deviceSelectorDialog = nullptr;
SoapySDR::Kwargs streamArgs;
SoapySDR::Kwargs settingArgs;
std::thread *t_SDR = nullptr;
std::thread *t_SDREnum = nullptr;
std::thread *t_PostSDR = nullptr;
std::thread *t_SpectrumVisual = nullptr;
std::thread *t_DemodVisual = nullptr;
std::atomic_bool devicesReady;
std::atomic_bool devicesFailed;
std::atomic_bool deviceSelectorOpen;
std::atomic_bool sampleRateInitialized;
std::atomic_bool useLocalMod;
std::string notifyMessage;
std::string modulePath;
std::mutex notify_busy;
std::atomic_bool frequency_locked;
std::atomic_llong lock_freq;
FrequencyDialog::FrequencyDialogTarget fdlgTarget;
std::string activeGain;
std::atomic_bool soloMode;
SDRDeviceInfo *stoppedDev;
#ifdef USE_HAMLIB
RigThread* rigThread = nullptr;
std::thread *t_Rig = nullptr;
#endif
void initAudioDevices() const;
};
static const wxCmdLineEntryDesc commandLineInfo [] =
{
{ wxCMD_LINE_SWITCH, "h", "help", "Command line parameter help", wxCMD_LINE_VAL_NONE, wxCMD_LINE_OPTION_HELP },
{ wxCMD_LINE_OPTION, "c", "config", "Specify a named configuration to use, i.e. '-c ham'", wxCMD_LINE_VAL_STRING, 0 },
{ wxCMD_LINE_OPTION, "m", "modpath", "Load modules from supplied path, i.e. '-m ~/SoapyMods/'", wxCMD_LINE_VAL_STRING, 0 },
#ifdef BUNDLE_SOAPY_MODS
{ wxCMD_LINE_SWITCH, "b", "bundled", "Use bundled SoapySDR modules first instead of local.", wxCMD_LINE_VAL_NONE, 0 },
#endif
{ wxCMD_LINE_NONE, nullptr, nullptr, nullptr, wxCMD_LINE_VAL_NONE, 0 }
};
DECLARE_APP(CubicSDR)

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -0,0 +1,518 @@
/* XPM */
static char const *cubicsdr_xpm[] = {
/* columns rows colors chars-per-pixel */
"256 256 256 2 ",
" c #010101",
". c #0A0A0A",
"X c #171817",
"o c #0D0F12",
"O c #221E1A",
"+ c #28251C",
"@ c #111929",
"# c #1D273A",
"$ c #1F2B3E",
"% c #1A2635",
"& c #1A2328",
"* c #272828",
"= c #212C3E",
"- c #232D33",
"; c #333434",
": c #3A3E3E",
"> c #363A3A",
", c #2D302F",
"< c #2A1D27",
"1 c #463B37",
"2 c #3B413C",
"3 c #4B463A",
"4 c #64603E",
"5 c #1F2B42",
"6 c #1D2B4B",
"7 c #1C2B55",
"8 c #222D41",
"9 c #222D4A",
"0 c #282F45",
"q c #243145",
"w c #25324A",
"e c #2B364B",
"r c #383E44",
"t c #283656",
"y c #323C58",
"u c #222C55",
"i c #131268",
"p c #131478",
"a c #091173",
"s c #1B2B6B",
"d c #283967",
"f c #2A3473",
"g c #15145D",
"h c #4F394A",
"j c #3D4243",
"k c #3B434A",
"l c #36425B",
"z c #3C4C6C",
"x c #374567",
"c c #374974",
"v c #2E4473",
"b c #414646",
"n c #42474A",
"m c #444A4B",
"M c #4A4D4D",
"N c #474846",
"B c #534D4D",
"V c #594B4A",
"C c #4B504F",
"Z c #454B51",
"A c #484C54",
"S c #4C5252",
"D c #4D535C",
"F c #525555",
"G c #545B5B",
"H c #5A5C5C",
"J c #565756",
"K c #57504E",
"L c #654C4C",
"P c #675656",
"I c #765656",
"U c #6F4F50",
"Y c #5D615C",
"T c #716957",
"R c #424C6B",
"E c #595F60",
"W c #545969",
"Q c #425273",
"! c #495577",
"~ c #4B5370",
"^ c #6F5668",
"/ c #5C6363",
"( c #5B666A",
") c #5B6679",
"_ c #646464",
"` c #636B6B",
"' c #6B6C6C",
"] c #686666",
"[ c #756868",
"{ c #6B7373",
"} c #697577",
"| c #747473",
" . c #737C7C",
".. c #7B7B7B",
"X. c #797776",
"o. c #6F6D72",
"O. c #8D5354",
"+. c #AF5254",
"@. c #8A7370",
"#. c #936A65",
"$. c #D05456",
"%. c #EF5557",
"&. c #FF516C",
"*. c #FA516C",
"=. c #D46768",
"-. c #7D817B",
";. c #FFFA01",
":. c #FCFA09",
">. c #FBFB16",
",. c #FAFB2B",
"<. c #998976",
"1. c #918773",
"2. c #A4917B",
"3. c #8B835E",
"4. c #F7FB4E",
"5. c #F0FB6D",
"6. c #ABF975",
"7. c #131788",
"8. c #131994",
"9. c #121B9B",
"0. c #1A1C97",
"q. c #0C188F",
"w. c #2C328E",
"e. c #121DA3",
"r. c #121EAC",
"t. c #071DA7",
"y. c #1221B3",
"u. c #1222BB",
"i. c #1D25B6",
"p. c #0F22B2",
"a. c #2A31AF",
"s. c #1C258F",
"d. c #34488E",
"f. c #3748B9",
"g. c #394BB1",
"h. c #4D5292",
"j. c #596489",
"k. c #6E7089",
"l. c #4D53AF",
"z. c #6868B2",
"x. c #625EAA",
"c. c #1224C4",
"v. c #1226CC",
"b. c #1B27C6",
"n. c #1228D5",
"m. c #122ADB",
"M. c #1327D2",
"N. c #0A27CE",
"B. c #2A33CF",
"V. c #112CE4",
"C. c #112EEC",
"Z. c #1B2EEC",
"A. c #0532FF",
"S. c #0C32FF",
"D. c #0C38FC",
"F. c #1232FE",
"G. c #1C33FE",
"H. c #1332F7",
"J. c #0F2EEE",
"K. c #2633FF",
"L. c #2B33FF",
"P. c #2734F7",
"I. c #3336FF",
"U. c #383AFE",
"Y. c #2933E7",
"T. c #423DFF",
"R. c #394AC6",
"E. c #3E51C7",
"W. c #3A4BD6",
"Q. c #114FFD",
"!. c #334BF7",
"~. c #0E70FE",
"^. c #2371FB",
"/. c #4458C9",
"(. c #4E57CE",
"). c #4F64D2",
"_. c #6F70D0",
"`. c #4845FF",
"'. c #5453FB",
"]. c #556DF8",
"[. c #716FFB",
"{. c #645BF1",
"}. c #403D95",
"|. c #947586",
" X c #A57B91",
".X c #847AB3",
"XX c #847BFD",
"oX c #837AD4",
"OX c #7B8484",
"+X c #7C8585",
"@X c #7D8493",
"#X c #7D88AD",
"$X c #088FFE",
"%X c #04AFFF",
"&X c #229BFB",
"*X c #728CF8",
"=X c #04D0FF",
"-X c #2BD1FE",
";X c #2FF1FB",
":X c #08F3FB",
">X c #5EF2ED",
",X c #838383",
"<X c #878B8B",
"1X c #989589",
"2X c #8A9494",
"3X c #969898",
"4X c #8F8B8F",
"5X c #A89987",
"6X c #B99798",
"7X c #AC8E90",
"8X c #A6A499",
"9X c #B8A897",
"0X c #B0A68F",
"qX c #B494A6",
"wX c #9AA5A6",
"eX c #A7A9A8",
"rX c #B7B8B7",
"tX c #B3B0AF",
"yX c #9396A6",
"uX c #C6B099",
"iX c #C598AB",
"pX c #C7A8A7",
"aX c #CCB3B0",
"sX c #D2A4A5",
"dX c #A9F7AC",
"fX c #CEC6B5",
"gX c #E9ECA5",
"hX c #9995FE",
"jX c #8E8AFA",
"kX c #A39CFF",
"lX c #A49DEB",
"zX c #AAA6FE",
"xX c #B3ACFF",
"cX c #B9B6FE",
"vX c #B6B6EB",
"bX c #9EA1D7",
"nX c #D4B4C6",
"mX c #C4BBFD",
"MX c #E9B6D1",
"NX c #B8C5C6",
"BX c #9BF3DA",
"VX c #C7C8C7",
"CX c #D3D4D3",
"ZX c #EDD1CF",
"AX c #F0E8D3",
"SX c #C9C7FE",
"DX c #D8D6FF",
"FX c #D3CCF7",
"GX c #EBD6F6",
"HX c #D6E7EB",
"JX c #E9E7FF",
"KX c #FEFEFF",
"LX c #F5F5FB",
"PX c #F0EEEC",
"IX c #D4F7CB",
"UX c None",
/* pixels */
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX*.&.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX*.&.*.&.&.&.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX&.*.&.*.*.&.*.&.&.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX*.*.*.&.*.*.*.*.*.&.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX*.*.&.*.*.*.*.*.&.&.*.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX&.*.&.&.&.&.*.&.&.&.*.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX*.*.&.&.&.&.&.&.*.*.&.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX*.*.&.&.&.&.&.&.&.&.*.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX&.&.&.&.&.&.&.&.&.&.*.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX&.*.*.&.&.&.&.*.*.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX&.*.&.&.&.*.&.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX&.&.&.&.&.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXsX*.%.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXsX%.+.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX9X=.+.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX| 7X#.O.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX[ 6X@.I UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX@.7X@.I UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXX.3X[ P UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX| 7X| L 1 UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXX.3X@.L V UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX@.3X@.K 3 UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXX.1X| L h UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX@.3X@.P 3 UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX@.4X| P L UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXO.O.+.=.$.$.=.=.=.$.$.$.+.+.+.O.O.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXO.O.#.O.O.+.+.+.$.%.%.&.%.%.%.%.$.$.+.+.+.O.+.O.U UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXI #.I I O.I O.+.+.+.$.$.%.%.%.%.%.%.%.$.$.+.+.O.O.U L O.U U O.UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXV ] I P U I I O.O.O.O.+.+.$.$.%.%.%.%.%.%.%.$.+.+.+.O.O.O.L U U L L L I L UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX[ _ P P P P I I I I O.O.+.+.$.=.$.$.%.%.%.$.$.$.$.+.O.O.O.L L U U U L h V L L UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX_ [ P P P ] _ I ^ I [ I O.#.#.+.+.+.+.$.$.$.$.$.$.+.+.O.O.O.L O.U L V V L L L V N V V b j j : ; UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX> : j : n [ _ Y _ _ I _ ] P I [ T P [ O.O.O.O.#.#.+.+.+.+.+.+.O.O.O.U O.I H U V U L K L V V B V V N m M N M M C M M M b j : : > UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX> r 2 j b j b N m M M C S S C F S C M M M F _ _ T ' ] ] ` ] [ ' ] [ [ ^ [ [ [ [ [ O.#.#.#.O.#.[ #.I U U I L I H P H J J K V B A N h N h N b m N N n m m N m M M M B K C D A B n b b : : ; UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX: j j b b b N M M C C C C S S C B C C B Z C M C B M M M m Z M N m M m m M B Y _ ] ' ' ' o.' ' ' ' o.[ [ o.[ [ [ [ [ [ ^ [ #.[ ^ [ #.] I [ _ _ T P Y P P P F J K M B m N N n 1 M B M B M M B M M N N N N b Z Z n m M M M M C B C B B M b b 2 2 > UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX2 j j b b b b m C B S F S S F F F S C C M C C B M M M m M M M M M Z M M M M M M M M N M M M V M M B B B C M M / o.' o.| X.X.X.X.......X.X.| X.| | X.o.| | | [ [ o.' ' ' ' o.[ ] [ [ ] ] _ _ E J G F F K N B N n N b N N n V M N n N m V m B M M N M m m n N N m m n b b M m M M M M M M M , , , & & O X UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXj k b m m C S S S S S F S G C S S C S S S C M A M m C Z M M M M M M M M M m C A C M M Z C B B M M S M B B B M C C A N C B m C B B C M M V S ' o.X...k.,X,X,X<X<X<X4X,X<X,X..@.+X@.-...X... .X. .X.| o.| | o.| o.' ' ' ' _ _ H H J F B M M N N b j h N m h b N B N N n B N B N n B b M N n n m m m M m n b m N m m j m 3 S F J D J H J G H G F H F M M m m 2 UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXG Y G G G S S C C C M M M M M M M M m M M C C C m M M C C C C C C M M M M C C M M C C C M C M M M M Z M M M M C M C B M A C M C B M A B K K K K B B G { | ,X,X1X<X3X3X3X3X4X3X1X4X4X3X3X2X<X<X<X<X,X@.1.k.-...X.X.X.X.| X.| | ' ' ] ] Y H J F S B N M b b j : V N N V N N B N n V b B V m B N N B B M M M M N N M m B B N C C C C N b b b b b N b j b b j N b N M m m M m N b m 2 2 2 > > UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXG G S S m m m M m m m C Z M M C C C Z C C C M C C C S C C C C M C M M C M M C A C C M A M B M Z M M A M M C C B M M C C M M B B B B B M M B K B A M B C M F H ' X.,X3X3XwXeXtXeXeXeX8XwXwX8X3X3X3X3X3X3X4X<X<X,Xk.,X....| | | | | ' ' ] _ E H J F S S B M N N M V V V m V M V B M V B B m M Z M M m M N N M M M m B V M N C N C m m m C B M M M M M B M M M M M M M M C C m m m C C m m C C M C C C C C C m m m b b m UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXS G S S S S S S S S C S S C C M M Z M M M M Z M M M M M m Z M C M C A M B M C C C C C M B M S C B C B C C C M B M A M M B C B C B C M K C B K B B B M K B B K B M K J I _ | { @.,X4X4X4X3X3X3X3X3X3X3X4X3X4X1X4X<X,X1.OX@...X.X.| [ [ ' [ ] _ ^ ^ H P P P S K B M m N V B M V N M B M B B n M V N M M M B m M M m M m M m M B C m C m C m N M m M m m m M M M m M m M m M m m m M M m C m b N m m m m m b m m m m m C m m C M S C m m UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXE G F G G S S C S C C C C S S Z S M C C C C Z m C C Z m C C C M M C m C M C M C A M M C C M C C C C C M K B B C C C C C C C C A B B C M M B Z M B K K B B C A B K S K P J U S J ^ P P I I ^ #.O.#.#.#.#.#.=.=.$.+.$.=.=.#.#.+.=.#.+.O.O.O.O.O.I I I U U K U K L L B F M M M V K M B B B m M B M B B m M M m M N M M M M m M N C N C C m m m C m C m C m C m m m M m m m m m N m b m m b m m m m m N m m N m m m m b m j m b b j j b m m b C m 2 UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXS / E E E G G G G G G G G G S S S S S S Z S S C C C C C C C Z m C Z M C C Z C m C M M Z M M C M M C M M C C M M C C C C S m C C C C C M M B M K K K M B C M Z C C B B S L L S L J P P I I I I I O.O.O.#.O.+.+.+.+.$.$.$.$.$.$.=.$.+.+.+.O.O.O.O.O.I I U U J V K K K K L L M B M M B B M M M B B M M M B M M M m M M m M B M M M M C m C N m C C N N N m m m m m m m m m N m N m m m m m m m b m b b m b m k m b m j m m b m b b m m m m m m b j b m b 2 UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUX2 ( G / / / / / / / Y G G G G G G G S S S S S S S S S S C C Z C C C C C C M C M C Z M M M C M C C M C M M M M M C M C M C Z C C C C S M C B B M B M C B C M C M B M B S C S P K B P K L U L P U I O.I O.O.+.+.$.=.$.$.$.$.$.=.%.$.$.$.+.+.+.+.O.O.O.I U U L L P K L L B V M B K A V B N M M C B C m B N B M M M M B M B N m M N M M m m m m m m N m N C m m m m m m N m b m m m m b b b m b b b m m m m b m b k m j k m m k b b k m m k j m b m m k b b m m m 2 UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXG / ` ` ` ` ` ` ` ( ` / / Y G G G G E G G G S G S S S S S Z S S C C C C Z C C m m C M M M M C M M C C Z M C M C C C M C C C C C C S M M M S F M M S C A B K B C A M B B B B B A B L P L P P P P O.I I O.+.O.+.+.+.$.$.%.%.%.%.$.$.$.$.$.+.+.O.O.U U U U U L L V S B V A K V M V B M B K K N C m m B M B M N M M M N M n m m m N m n N m N m m m m m m m b m m m m m n m b m b m b m m m b m m m m b m b m k m m m m m m m m m m m m m m m m m m m m m m b k b k m UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXC / ` ` { ` { ` ` { ` ` ` ` ` ` / ` Y / / G G G G G F G S S S G S S S C S S S Z C C Z C Z C Z m Z M M Z M C C M C M M M M M M C m C m A M M B C C M M M B K C M C K C K A L B A B C L L K J P U U U U U O.O.+.+.+.$.=.$.$.%.%.%.$.$.$.$.$.+.+.+.O.U U U U L U L K K A V m M V N B V M M M N m m N C M N N N M N M b n N m m N n N m m N b m N m m m m m m b b k m m k m m m k m m j m m m m m m m k m m m m m m m m m m m m m Z m Z m m m m m m C m M m m m b m j j m UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUX{ G ` ` ` { { { ` { { { ` ` { ` ` ` ` ` ( ` ` ` / / G E G G G G G G S S S S S S S S S C C C C C C Z M C M M m C Z M C M M M M C C m S M M C M C M M M B B M M C B B K B B S B B C S K L D U L U L U O.U O.U O.+.+.+.$.$.$.$.$.$.$.$.$.+.+.+.O.O.O.U O.O.U V V V K V B V M Z m M m M N b M N N C N N m M m M n m b M b m m b m m m m N m m m m b m b m b m m m m k m m b m m m m m m m m m m m m m C m m C m Z C m M C m C C C C C C C C C C C C C C m m M N m m b m 2 m UXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUX{ G ` ` { { { { { } { { { .{ } { { ` ` ` { ` ` ` ` ` ( / / Y G Y G G G S S G S F S S C S C C S C C C C C C C Z C M Z M M M M C m C m M M M M B A M M C A M M A M B M A M K C K B B S K U L U L L O.U U O.O.+.+.+.$.+.$.%.%.$.$.$.$.$.+.+.O.O.O.U L L L N L V B V Z n V m M N m m N m m N M n m m m b m m b n m m b m m b N m m m m N m m m m m m m m m m m m m m m m M m m Z m M m C C m m S C m C C C C C Z S C C C Z S S C C C Z C M C C C Z Z M m M m M m b b b j k > UXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUX, ` G ` ` ` ` { { } { { .{ .} { . .} } .{ { { ` ` { ` ` ( ` ( / / / / E E G G G G F F D D S S S S C S Z C C C m M M M M M M C C m C M M m Z M M M m Z C M M M M B B M M M A C A B M B A h L L U U U U O.O.+.O.+.$.$.$.$.$.$.$.$.+.+.O.O.+.U U V L L V B N V N N B N M N N N N n m b M n m N b m m m m m m N m N m m N M m n N m b m m m m m m m C m m C m m C C C S C C C C S S C C S S S S S S S S S S S S S S S S S S C S Z C C C C C Z M C Z Z M m m m k m b j b j 2 UXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUX; ` G ( ` ` ` ` } ` { } { } . .{ . . . . . . .} .} { { { { { { ` ` ` ` ( / / Y Y G G G G G S G S S S C C C S C C S C C C C C C C C m m Z m Z m C C m C m M C C C S m M M M B M B M M V M B V L L L U O.O.O.+.+.+.$.+.$.+.+.+.+.+.O.O.U U U L V V b V B N N m N m m N M N m m m m m N m m b m m m m m m m k m m m M m m m C m m C Z m S m S S C S S S C C Z C S S Z S S S G S S S G S S G S S S S G S S S S S S S S S Z C C S C C C C m M C M M m m m m m b m k m j j j 2 > UXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUX> ( G / ( ` ` ` ` } ` } ` { { { . . . . . . . . . .-. . . .} .{ } { { } ` ` ` ` ` ( / / / Y Y G G G G G G G S S S S S S S m C C C C C C C C C C C m Z M M m m m m m C M M M m B N M M N V B V V L L U U O.O.+.+.+.+.+.+.+.1 +.O.U 3 L U 1 V V N V N b m N B m b m m m m N m m m m m m m m m m m m C m m N M Z C C C C C S S S S S S S S S S G S S G S G G G G S G G S G S S G S G S G G S G S G S S S S S S S S S S S S S Z C C C m C Z M m m m m m m m k m m m k j j j 2 M UXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUX2 ( G / / / ` ` ` ` ` ` ` { ` { { { { . . . . .OX-. .OX-.OX . .-. . . . .{ .{ { { { ` ` ( ` / / / / E G G G G G G S S S S S S C C C C S C C C C C Z C m C Z C C C m C M m N M M M m M V V N B V V L L U U U O.1 O.O.O.O.O.O.1 V V B B h V N N N m N n m m m k m m N m m M M m M M M M C C C C S C C m S S S S S S S S S G G S G S G G G G G G G G G G G G G G G G G G G G G G S G S G S S G S S S S S S S C C S C C C C Z m C m m m m m m m j m m m b 2 m m b 2 b k 2 j > C UXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUX2 / F Y G Y / ( / ` / ` ` ` ` { { { ` } .} .} OX . .OXOX+X+X .+XOX+X+X .OX . . . .} .{ { ` { ` ` ` ` / Y Y Y / G G G G S G G G S S S S S S Z C C C S Z C C C m m m C m m M M m m m m m B V M V h V V 3 V V K 1 L 1 1 1 1 1 1 C k N m 1 h V b m M m M m C C C C C C M C C C C C S S C S S S F G S G G F G G G G G E G G E G G G E E E / G G G G G G G G G G G G G G G G G G S G S G S G S S S S S S S C C C M C Z m m m m m m m b m m m k m b m j b b b b j b j b j j j > C UXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUX2 / D E / G / ( / / / ( ` ` ` ` ` } } } { { { { . . .-. .+X+X+X+X+X+X+X+X+X+X+X+X-.+X+X . . . .} } } { } ` ` ` ( / / / ( G Y G G G G S S S S S S S S S C S C C C C C m C m M m m m m M m M m m N M N M C m C C m m C m 1 h m m M C Z M Z M C C M C C C S S S S S F S F F S S G D G G G G G G G Y G G Y / / E / / / / / / / G / G / G / / / ( G / G / G G G G G G G G G S S F S S S S S C S C C S C C Z C m m m m m m m m m m m b m m b m b m 2 b b m b j j m 2 j j j 2 m > M . UXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUX> / S G G G G G Y / / ( Y ` / ` ` ` ` ` { { { { { .} . . .OXOX+X+X+X+X+X+X2X+X+X+X+X+X+XOXOX+XOX . . .} } } } } } ` ` ( ( / Y Y G Y G G G G G G S S S S S S S C C C Z C C M M Z C M Z M m C C m M M M m m m C C C C S V V C C C C S C S S F S S G F D F G G G G G G G / E / G / / / / / ` / ` / ` ( / / / / / ( / / / / / / / / / / / G G G G G G G G G G G S G S S S S S S S S S C C C C m m Z m m m b m m m m m k m m j j m b b b b 2 m b m j b j b j b j m j j j r j > m o UXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUX2 E S G G G G G G G / G Y / / / / ` ` ` ` ` { { { { { . . .} -. .+X+X-.+X+X+X+X+X+X2X+X+X2X+X+X+X+X+X+XOX-.OX-.} } } } } } ` } ` / / / / G E G G G G G G S S S S C C C C C C C C C C C C C S S C S C C S C S S S S G S B S D F D S G G G G G G E / E E / / ` / ( ` ` / ( ` ` ` ` ` ` ( ` ` ( ` ` ( ` ` ` / / ` / / / / / / / / / G G G G G G G G G G S S G S S C S C C S S C m C m m m m m m b m m m m m m m b k b b b j b b j b j b b j m j j j b b k b b 2 k j j j j j : m X UXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUX; E M G G G G G G Y G / G Y / / / / / ` ` ` ` ` { ` { } { } } . . . .+X+X+X+X2X+X2X+X2X+X2X2X+X2X2X2X+X2X+X+X+XOXOXOX . . .} } } } } ` ` ` ` ( / / E E E E G G G G S G D S D S S S S S S S S D S F F F G G G G G G G G J Y Y / / / / ( / ` ` ( ` ` ` ` ` ` ` { ` ` { { ` } { ` ` { ` { ` ` ` ` ` ` ` ( / ` ` / / / / / E / G G G G G G G G G S S S S S C S S m C C m m Z m m m m m m m m m m b m j m k b b b m b b m b m j b m b b b j j j j b b j j j 2 j j 2 j j j 2 j > m & UXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUX; E C D G S G G G G G G / G G / G ( G / / / ( ` ` ` ` { ` { { { .} . . .OXOX+X+X+X+X+X2X2X2X2X2X2X2X2X2X2X2X2X2X+X+X+X+X+XOXOXOXOX} } . .} } { { ` ` ( ( ( / / G G G G G S G G G G S G G G G / G / / ( ( ( ` ` ` ` ` ` ` { } { ` } } } { { } } { } { { { { { { { } { { { { { } ` ` ` ` ` ` ( ( / / / / / / / G G G G G G G G S G S S S S S S C C C C m C m m m m m m m m m m m m m m k m m b b m b b m j b m b j b j j b b b j j j k b j k 2 b 2 j j j j j j j 2 j j 2 > m & UXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUX, / M G G S G G G G G G G G G G G E E ( / Y / / / ` ` ` ` ` ` { { { { . . . .-.+X+X+X+X+X+X2X+X2X+X2X2X2X2X2X2X2X2X2X2X2X+X2X+X+X+X+X . .OXOX . .OX . .} } } ` ` / / / G Y G G G G G / G / / ` ` { { } } OX . . . . .OXOX . . . . . . . . . . . . . . . . .} .{ { { { { ` { ` } ` ` ` / ` / / / / / E G G G G G G G S S S S S S S C C m C m m m m C m m m m m m b m m b b m b b b b b m b b m b b b b b b b j j b b j b b j j j k 2 j j j j 2 j j j j 2 j j j j j 2 r j > b & UXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUX- G m G S G S G S G G S G G G G G / G E E / E / Y ( ` / ` ` ` ` { { { { } . .} -. . .OXOXOX+X+X2X+X2X2X2X2X2X2X2X2X2XwX2X2X2X2X2X2X2X2X2X+X2X+X2X2X2X+X+X+X+X} } { ` ` ` ( / / / / / / ` ` { .OX2X2X2X2X2X2X#X2X2X2X+X+X2X+X+XOX-.OX .OXOX .-.-. . . . . .{ { { { { ` ` ` ` / ( / / E Y G G G G G G G G S S S S S C S S C C Z C m m m C m m m m m m m m m k m m m b m b m b m b m m b b b b k k b b j j b j j j b j j j j 2 j j j j 2 2 j j j j j j j j j j j j 2 j j j > m X UXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUX, / m S G S G S G S G G G G G G G G G E G E G / / / Y Y / ` ( / ` ` ` ` { } } } . . .-.-.OX+X+X+X2X+X+X2X2X2X2X2XwXwX2XwXwXwX2X2XwXwXwX3XwXwXwXwXwXwXwXwXwX2X@X+XOX . . .} { { } ` ` } { .+X2X3XwXwXeXrXeXwXwXwX2X2X2X2X2X<X<X+XOXOXOX . . .} { { { { ` ` ' ` / ` / ( / / / G E G G G G G F F S C C S S C C C C C m C Z C m m m C m m m m m m b m b m k m m m b b m b b b b b b b b b b j b 2 b b 2 b j j j j b j j j j j j j j j j j j j j j j j 2 j j j j 2 j r 2 j 2 > M X UXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUX& Y m C m S S G G G G S S G G G G G G G G G E G G G E / E / / / / ` / ` ` { ` { { { . .} . .-.+X+X+X+X+X2X2X2X2X2X2X2X2X2XwX2XwXwXwXwXwXwXwXNXNXNXNXNXrXwXrXwXwX+X@X@X+XOX-. . .} . .OX+X2XwXeXrXNXNXNXrXeXrXwX2X2X2X+X+X+X+X . . .} } { { ` ` ` ` / / / / / E G G G G S G F S S S S S S C Z S C C Z Z C C m C m m m m m m m b m m b m m m b m b m b m 2 m k b b m b b b b k m j j k b j j j 2 j 2 j j j j j j 2 j j 2 j j j j 2 j j 2 j j j j j 2 j j j 2 r 2 j r j j ; m X UXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXX / m C m k m m C S S G G S G S G G G G G E G E G / / G E / E / / / / / / ` ` ` { ` { { } } . . .+X+X+X+X+X+X+X+X2X2X2X2XwX2XwX2XwXwXwXwXrXNXNXNXCXHXCXHXIXNXNXwXwXwXyXwXwXwX2X2X+XOX .+X2X2XwXNXNXNXNXdXeXwXwXwX2X2X+XOX . . .} } ` ` ` / / ` / / / G G G G G G G G S S S S S S S C C Z C C S C C C m m m C m m m m m m m m m m m m b m k b b m b b b k m m 2 b b k 2 j b j j j j j j j j j j j j j j 2 j j j j j j j j j j j j j j j j j j j j j j j j j j j j j 2 2 j > k X UXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXo / m S C m k m k k m S G S G S G S G S G E G G G G G G G E Y / / / / / Y ` / ` ` ` ` { { { { { . . .OXOXOX+X+X+X2X2X2X2X2X2X2XwXwXwXwXrXNXNXNXHXHXHXLXLXJXHXIXNXrXeXrXrXNXrXwXwX+XOXOX+X2X2XwXwXeXwXwXwXwX2X2X+XOX .} } } { ` ( ( / ( G G G G G G G G G S F S C S S C S C S S S C C C C C m m m m m m M m m m m m m m m m b m b b b m b b k m b b b m b b b m b m 2 b b 2 j b j j k j j j j j j 2 j j j j j j j j j j j j 2 j j j 2 j j j 2 j j j j j r 2 j 2 j j j j 2 > Z o UXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXY k G C C m j m m k m k m S S S G G G G G G G G G G G G / G G / G Y / / Y / / / / ` / ` ` { ` { { } } } OX} .OXOX+X+X+X+X2X2X2XwXwXwXwXwXNXCXHXHXLXLXLXLXLXHXNXNXNXVXBXHXIXNXwX2X+XOXOX+X@X2XwXwX2X@X2X+XOX-.} ` ` / / Y Y G G G S G G G F S S S S S S S S S C S Z C C C Z m C m Z m m m m m m m m m k m m k j m b b b b b k b b b b b b b b 2 m 2 k b 2 b 2 2 k b b k b b 2 j j 2 j 2 j j j j j j j j j j 2 r j 2 j j j j j j j j j j j j j j j j 2 j j j j 2 r 2 2 j > C o UXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUX/ m M M S S k m m k m 2 k k 2 C C S G G G G G G G G G G G G G / G / G G / / / / / / ( ( ` ` ` ` { { ` { } } .-.-.-.OX+X+X+X+X2X2XwX2XwXwXNXNXIXHXLXKXKXKXLXHXVXNXCXHXHXJXHXNXNX2X+XOX . . .OX .OX .} } { ` ` ( / Y G G G G G G S S S S S S S S S S S C C C C M M M m M m m M m m m m m m m m N m n b n b b n n n b b b b b b b b b b b b j b j b j j j b j j b j j 2 2 j j j j 2 j j j j j j j j j j j j j j j j j r j : 2 b r b j b r j j j r 2 j j : 2 j k j : j : j ; k o UXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUX/ m S C C S S m k k m m m m k b m k m C S S G G S G G G G G G G G G / / G G / E / / / / / / ( ( ` ` ` { { { { } . . . .-.+X+X+X2X2X2XwXwXrXNXCXHXHXLXLXLXLXHXNXNXCXHXLXLXHXHXrX2X+X} } { { { } ` ` ` / / / Y Y G G G G G S S S S S C S S S S C C m C m M m m m m m m m m m m m m b m j m j m b n b n b n b b j b b b b b b b b j b j r j b j b j j j b j j j j j j r b 2 2 j j j j j 2 j j j j 2 j j j j j 2 j j b b b b r j j j : j j 2 j j j r r r k r > k j : 2 r j ; m . UXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXE m S m m C Z S m b m k m 2 m m b 2 m b b m m S G G G G G G G G G G G G / G E G / G / ( E / / / / ` ` / ` ` { { } } } . . .-.+X+X+X2X2XwXwXrXNXNXHXPXLXLXHXBXNXNXNXHXLXJXHXNXwX2X .} ` ` / / ( / / / / Y G G G G G C S S C C S C S C C m C m m m m m m m m m m m m m m k m j b b m b m m m b b b n b b b b b b b j b j r b b b r b b b b b j j j j j j r j j j j j j j 2 j j j j j j j 2 j 2 j j j j j j j j j j j r r r 2 b r b b n n j j r j r j : j r k C j j 2 : 2 > m UXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXF m C C Z C m m m b m m k m 2 m k m b k m b j k m C G G G G G G G G G G G G / G / G / E E / / / / / / / ` ` ` ` ` ` { { } } .-.+X+X+X2X2XwXwXNXNXCXHXHXHXCXNXwXwXNXCXHXHXCXNXwX+X} } / / / / G / G G G G S G G S S C C S C C S C m C C m m m m m m m m m m k m m b m j m m b b m b m j j b b b b j b b b j b b j b b b b 2 b j b r b b r j k j j j j j j j 2 j j j j j j j j j 2 j j j k j k j j j 2 j 2 : j 2 j b Z M F S D D A A Z m r r : : j : r 2 k M Z j j j : j ; M UXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXS m S m m C m m m b , > m m m k 2 m k m m j m m m m k k C S G G G G G G G G G G G / G E E E / E / / / ` / ` / ` ` ` ` ` ` { { } . .-.+X2X2XwXwXrXBXNXNXCXNXNXwXwXNXBXCXNXNXwX2X+X} ( / / / G G G G S S G G S m S C C C C m m m m m m m m m m m m b m m m b m m b b m m b b b b j m j k b b b b j b b b j b j j b j j j j j j b b r b j j j j j j j j j j j j j j j j j j j k j m j j b r : : > j b n Z A D D W W D W D D D C M M M n M b : j 2 k r j k m M C j : : j j ; m UXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXS m Z C S m m C m m & o & 2 2 m m m j m m b j k m j b m b C C S G G G G G G / G / G E E E / E E E / / / / / / / / / ` ` ` ` { } } . .OX2X2X2XwXrXNXNXrXrXwXwXwXwXrXNXdXNXwX2X .} / G G G G G S G S C S m C C m C m m m m m m m m m m m b m j m m j k b b j m k b j j b b j b b j b b b b j b b j b j b j b b j b j j b b j r b b 2 j j j j j j j 2 j : j j j j b j j j j 2 > ; > 2 b S D ~ W ! ) ! ) W W ~ D D C A N N N M N M B A M j : j j : j j m Z Z m j j 2 2 j ; C UXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXM M C C S S C C m C - . - 2 k m b m j m C m M C m m m C m m C G G G G G G G G G E / Y G E E / / Y / / / / ` / ` / / ` ` ` ` } } .OX+X2X2XwXwXwXrXwXwX2X2XwXwXwXeXwX2XOX} ` / G G G S S S S C C C C m m m m m m m m b m b m b b b j m m b j m m j b b j m j b b b b b j j m j b b j m j j j b j b j b b j j b j j j j j j 2 b j : r b j j j j j b 2 j : : > ; 2 k C ~ ( ) @X#XyXvXyX) j.W W A m M N N N N M M B M B C K B S B J b r : j j j k k C M M j > 2 r j ; m UXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXj Z S S S C m m m C 2 . . . . X ; C k m m j k M m m m m b m m C m C G S G G G G G G G G G E / Y G Y G / / / / / / / ` / ` ` ` ` } } } OX+X2X2XwX2XwX2X2X2X+X2X2XwXwX2X+X .` / G G S S S C C C C m Z m C m m m k m m b b m b b m m m b j b j m b j m j b b j m b b b j b m j j b j j j j b j b j j j b j j j j j j j j j j j j r b b b r j 2 j : : : j b A ~ ] ) k.@X@X2X|.k.2XrXCXNX<XN V N m B B M B M F B B B F F K F F J J H H b : r j j m m m C Z k 2 j r 2 2 ; k UXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXS M S C S C m C k 2X-., X 4XZ b b C m m b C M M m C M m m m m C F C F G G G G G G G G G / E G / / E / / / / / / ( ` ` ` ` { { . .+X+X+X@X2X+X+X+XOX+X2X2X2XOX .} ` Y G S S S C C C C m m m m m m b m b b b m b m b 2 b j b b b b k b b b j b b b b b j j b m j j j b j j b : b j j b j j j j j j r j j j b b b b : r b : > : 1 n n A R W j.j.x.k.k.k.k.) W _ J Z M > tXKXHXAXF M S B S B B F F S J K F J H H H H H H H H b > b k m m n S Z Z m j : > r 2 > j UXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXC M C N C S m m m M C { X.H * X <XH * * : m C k m C m m M m C C C C m m m C S G G G G G G E G G E G E E E E E / / / / / ( ` ` ` { { } .OX-. .OX . . . . . .OX-. .} ` Y G G S S C C m m b m m m m m b m b m m k b b b m m b b b b b b b b j b b b j b j b b j b j j b j j b b j b j b : 2 j j j j j j b j j r r > : : j b M A F W ~ ! ) j.) ) ) ~ D J h n m N m N M N B M CXPXPXLXJ A K K F J J J J J H H H H H H H H Y _ _ _ b r b m n m n Z M M k : : j 2 r > 2 UXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXC C C C S m m m m ; , Y ..| M . 1.' , o * 2 m 2 m C M M m m C k C S m m C m S C S G G G G G G G E E E E E E / / / / / / ` ` ` ` { } .} } .} } { { } .} } } ` Y G G S S C C m m m m m m m b k m b m k m b b b k k 2 m b b b b j b b b b j b j b b j j j j m k j j j 2 j j j j j b j b k j j > : : 2 b h Z A D W W ~ ! ) W ) D D M B B n n N N m M B C B B S B K S M CX3X4XCXN E J J E H H H H H H H _ P _ _ _ ] ] ] ] _ b b n k m m M M m M k : j 2 r 2 > 2 UXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXm C C C m C C C C j X . b ' .._ ,Xo.* o ` J k S M C C C C C C m C j m C C C C C G G G E G G G G E G E E E / / / / / ` ` { ` { { } } .} } ` ` ` { ` ` ` ( / G S S C C C m m m m b b b m m b b b b b b b b m 2 m m b b j b b b b j b b j b j j j j 2 j 2 j k j m m b b b r j : : : j b n D D G W ! ) ) ~ W W D A A M n N N B n B A B S K M V C B S K S F K J B ' tXY _ 3X_ H P E E Y _ Y _ _ ] ] ] _ ] ] ] ] ] ' | ] b m M m C n Z M m M m : j : 2 : > > UXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXk S M F C m S S N D J . b X.wXeX] O 2X; | H b C m S m C b m S S M C C C m C N C G G G G G G G G / G / / E / / Y ` ` ` ` ` } { { } } ` ` ` ` ` ` ` / / G G S S C m C m m m 2 m b m m b b m b b 2 m b m 2 b b 2 b b j m b b j j j b : b r b b b m m j m b 2 > > > > b n Z W W ! ) ) W ! W W D A A M n n n M M n M B B A B B M S B F B F J K J J G J H J H K X.2Xm o.NX_ E _ _ _ ] ] _ ] _ ] ] ' o.{ | | | | o.' ] n b m m m m Z Z M Z k 2 j : j : > - UXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXj S M S S S M M S S 3X* . 1 | _ 4X| ..' b . . <XN 3XF m b b m m m b C M C m C C C S m m C S C C G G Y G G E G E G / / / / / / ` / ` ` ` ` ` ` ` ` / ` ` ( Y Y G S S S m m m m m m m m m b b b b j b b b k k b b b b b b j j 2 2 k j b b n b b b b r r ; > j k m D ` ) j.k.j.k.j.! ) W W A D n C m M B M M B M B M B B B S B B B S K J F F F H H H H H H H _ H _ K 4X3XJ ] VX1._ [ ] ] ' ' ' { | | X.o.' ' ] _ _ [ ..4X,Xn Z n m Z M Z Z m m j j 2 : : : > * UXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXj S M C S S S C F M <XN * 4X' | ; X M | ..H ; ,X_ ,X_ m o . , > m m m m M j C S C C C m m S m b m C S S G G G G G G G Y / / / / ` ` ` ` / ` / / ` / ` ( / ( G G S C C C C m k m k m b b m j m b b b b b 2 b b k b 2 b j j k m b k b 2 1 : : r b b D / ` k.@XwXbXNXFXwXk.) ~ D B Z A m h M M B B M M M B B B B B S K J F F J J J H J E H P H H H H Y Y _ _ _ H [ J VX,X_ H VXPX' ' ' ' ' ' ' ] _ _ J | @.4XwXVXVXVXPXCXwXM C Z A Z Z Z M C Z k : : 2 j : > UXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUX2 S M J C C F S G F <X_ . X 3X_ X.] , H ,X] 8X' X._ C O X * k m 2 S S N C F m C C C C m m C m C m C G G G / G / G / Y / Y / ` / / / / / / G Y ( G G G G S S C k m m m m b b m b b b j b b b b b b b b b b m 2 m 2 2 2 2 2 b m D W ! k.k.<X|.#X4Xk.@XrXHXKXPXwXN N N V M M B M M B M B C B S K S F F J J J J H J H H H E P H E H _ Y _ _ _ _ _ _ _ ] o.] CXHX' o.] 3XKXPXk...| -.k.1.3XtXrXCXHXKXCXVXrXwX4XX.k._ r Z b M m Z M m Z Z j 2 r r 2 : > UXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUX> S C S C S C C G S +X| X 1X] | 3X. X , 8XeXeX,X > O . n ..m G S F S S M n m M M m k M m C m M S S F G G G G / E / / / / / / E E / / G ( G G G G S C m m m m k m b b b b b b b j b b j b j j b : : : b k k l ~ ! k.k.k.x.k.k.j.( ( W K M M r 3XLXLXKXLXJ M C M S B C B K S J F K J F J J H H H H H H E H H E _ _ H / _ _ ] ] ] ] ] ' ' o.o.' H tXKX4X' .o.-.rXVXPXPXHXPXLXPXPXCXVXVX,Xk.| X.1X1XrXVXCX2Xj A C C k C A C Z n j 2 r 2 : : ; UXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUX* S m G C F G G F m { ' . . k.4X' eXO . . _ 2X,XtX' | b . N eX. > k C S S m m m M m M M n C M C M F C S S G G G G G / G / E E / / G G E G G G S S S C C C m m m m b b b b b b j b b b j b j b n M A W W ~ ) j.! j.) ^ ~ W D B Z b h b m A n J > tXKXPXLXPXF B F B K F F J F J J H J H H H H H H H H P _ _ _ ] _ ] ] _ ] ] ] ] ] ' ' | | ' _ P X.tXLXeX] X.o.} ....| 4X4X<X|.,X| | ' -.<XeXVXNXCXbXbXoX}.0.d S k S Z C C k Z Z n j r 2 r : : , UXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXG N F N C G C J C { ' O | 3Xo.tXK 3 . F 4X' ..; | 4X| M o j 3X. 3Xo . * > k M k M n m C m m C M C M C M S G G G G G E G E G / G G G G G G G S G S S C C m m m b m b m b b b b b b b j j j n ~ W ! W ) W D D A A A n N m M M M M B C S B B B D b 8XPX4XtXPXB H J F P J J H H P P E H H / Y _ _ _ _ _ ] ] ] ] ] ] ` ] o.' | | | | { | _ ' X.eXVXHXCX-...1.k.,X1. . .k.| | ..1X1XrXrXVXvXbXyXz.w.0.a a i i a l Z Z k A C k S Z M n j 2 : : : j , UXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXS m F F S 4 F C F Y ' , J <X1.wXaXPX. ; <X{ |. X M b X.-.] > * 3XM VXO j ; > C n M j M C C m M N M m C S G G G G G G E G G G G G G G G G G S S S Z Z k m b m b b b b m n r b j b j b j n E D A A n n M N m M B M B M B M B B B B B S B B H n eX' m _ 8X] J H H H H H Y E E E _ _ _ _ _ ] ] _ _ ] ] ] _ ] o.| | | ' ' ' ] _ _ | k.8XVXPXCXrX4XX...-.-.X.| X.,X1X0XVXrXCXvXbXjX/.B.c.t.c.p.p.9.7.p p p 7.y n k C A k C m n M b r j 2 : : j , UXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXS C C C F Y F F Y N H 2 J ; b eXfXfXT & 3Xo.o.M ; > . * _ @.X.] eXH 4X> . j . X , j k M C b b b M m N M G G G G G G G G G G G G S G G S S S S S C C m m j m b b b b k b b b b j j j b j b D D h M B M M B B B A B B S K S K F F F J J J H J H rX_ _ B eXo.J E Y _ _ _ _ ] ] _ ] ] ] ] ] ' ' ' | o.| | X...o.] _ ` -.|.wXeXrXCXCXPXCXtXk.o.( o...OX1X2XtXVXVXHXkXhX[.T.P.J.J.u.t.9.y.i.b.i.y.e.8.p 7.7.0.R m M M Z m m Z Z Z j j 2 2 j : j & UXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXS C C Y F J F G Y 2 j M . J X eXCXX.1X O yX1.F ,XN > o j | VX3X8XK . b X O - j m b b j m j C S G G S S G S G G G G G G S S G G S S S C m m m m b m b b b 2 b b j j j b j j b b W A Z B M B M F B S K F F F J J J J J H H H H H H ] eXH _ F rXX.J ] _ _ _ _ _ ] _ ' ] ' o.| X.' ' ' ' ' ' ' _ ` @.3XrXVXLXCXVXVXCXrX<Xk._ o.,X3X0XVXNXVXSXIXgX4.4.-X~.A.D.A.S.S.Z.u.u.b.v.N.m.m.n.e.p i 7.7.0.n n Z Z M Z Z A Z Z k 2 r : r > 2 & UXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXC m S G F Y G F Y N ; C . H * tXAX+ uXX X 4X..N 3X_ ; o 1X3XtX3XY ; ; * . . & ; k k k N m S G S G G G G G G G G S G S G S S S C C C m m m m b m b k m b b j b b j j j b j b W D F K F J F F J J J J H H J H E H H P H E P ] B X.rXK o.M rXCX' | ' ' o.| | o.| | X.{ ' ] ] X...@.<X,X,X3XVXVXCXCXNXeX..} ] ] ' @.3X9XVXVXCXvXjXjX!.!.=X6.:.;.4.-XQ.G.G.G.F.S.J.N.n.v.M.M.M.e.7.7.8.r.9.7.7.n Z M Z Z Z n Z n Z m : r 2 j > j O UXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXm C C Y Y 4 Y Y / N > 3 X.[ o Y ; 8XAX 1X2 . O ,X,X2 8X@.[ . X k.....| Y <Xo.' ; o * ; j k G S G G G S G G G G G G S G S G S S S Z C C b m b b b b m 2 b b b j b j b b r b b W W S J J J J J H H H E H P E P _ _ E _ _ _ ( ` o.PXrXF X.H eXKXKXtXH ' H _ H E _ ] _ | 4X3XVXPXPXPXCXHXCXVXVXeX.._ ` X.1X8XVXVXVXHXbXbX{.B.P.A.A.D.D.Q.:XgX:.,.4.;X~.S.D.S.F.S.n.r.y.c.c.y.e.e.e.y.9.7.7.7.p n n Z n Z Z Z m Z Z j : 2 r : : j o UXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXk C m J F Y Y Y T K ; C rXAX . K n 3XAX <.3. . o 4X3X> wXeXLXK X @.<X] F O m ,X4X' : o , G S S G S G G G S G G G S S S S S S S C Z m m m b m k b b j b j j j j j j b j r b W ( J H H H H H H H / / Y _ _ ( _ ] ] ] _ ' ' E PXPX' o.' { ' rXLXLXHXVXVXVXVXVXVX8XVXCXKXAXrX2X4Xk.| | ' ' X.4X8XVXVXVXvXyXoXE.B.V.N.c.t.N.J.F.G.G.G.~.;XgX;.:.4.;X~.F.F.F.H.V.V.V.V.c.y.u.y.e.7.i g i p 7.f Z n n Z Z n Z C n C k 2 r 2 r > j UXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXj C C Y Y Y Y / Y / O -.uX5X, b S 3XPX T 2. . . ,X| , 3XCXAX8X o ,X3X/ H * K J ,X|.G * . X S G S S G G S G G S S G S G S G S S S C C m m m b b m m b b b b j b h j j j j b k ) ( J H E H _ _ _ _ _ ] ] ] ] ] ] ] ] ' | ] _ CXKX4X' | | | | X.X.tXtX,XeX3XVXVXHXCXVXeXOX{ _ | X.3X8XrXVXVXCXrXoXoXg.a.e.p.N.m.m.M.y.0.e.M.F.F.F.S.Q.%X;X4.:.;.5.-XF.F.F.F.H.F.C.u.e.y.y.e.7.i i i 7.7.7.7.i m Z M A Z Z Z Z Z Z j 2 2 r j > 2 UXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUX2 C m J Y Y Y Y Y ' o -.8X[ 3. ; G <XAX. , 0X . . X.X + 2XCX1.uXX . k.wX] ] , > X j { <X| M o o S S S G S G S S G S G S D S S S S Z C m C m m m m j b b b b b j b j j j j j j j r o.( G ^ _ ] ] ] ] ' ] ] ] ' ] o.' X.o.] H <XCXPX<Xo. .| X.X.| ..X. .| X.X. .] _ _ ` ] ..1X8XVXVXnXvXyX.Xx.a.a.9.q.a q.e.u.b.m.n.v.c.v.m.V.C.S.F.F.F.F.F.-X5.:.:.5.-XD.F.F.F.F.v.e.p.y.r.e.e.e.r.r.e.9.7.p i u Z Z n n Z m Z Z n n j j : : : > j UXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXC m G Y Y Y Y Y ' . X.8X; 8X O F 1.AX, . uXo [ . o +XfXY 8XN . . X.8XJ ' O O > O J X.,X' ; o S S S G S S G S S G S G S S G S S C C Z m m m m m m b b b j j b j b b j j j j j r k.) _ ' ] ' ` ' ] ' ' ' | | | o.' _ ' 3XVXPXrX' | | .X.k.1.OX1.,X1.k.X. .-.1X8XrXVXVXCXvXoXjXW.B.i.t.t.t.t.q.7.p p 8.y.u.n.C.J.F.S.S.J.V.F.F.F.F.F.F.$X;X,.>.:.dX%XH.S.F.J.m.v.c.u.c.v.M.v.c.e.7.7.i i i p u M b m Z Z Z Z m Z k : : j : : : > UXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXS m Y Y Y Y Y ` ' O | 8X uX ; S | AXB 0X; . _ . X.CXj 5X<. . ' 8XJ / E ; O : . ; ] ,X-.Y + C G S S S S G G G S G S S S S S S Z C C m m m m k b b b j b j j b j j j j j j j r k.k.( ' ' ' o.o.X.X.| ' ` Y ] @.<XrXCXCXtX..o.-.1.+X1.k...1.-. .k.1X5XrXVXVXFXlXoX_.W.B.N.N.p.p.p.y.i.a.0.0.7.9.y.u.y.u.u.v.M.n.c.e.e.u.M.J.F.F.F.F.D.%X;X4.>.;.dX~.H.A.F.F.H.F.C.m.b.9.8.7.7.8.8.7.i g i 7.f C k m Z m Z Z Z Z n j 2 r : : 2 ; UXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXC m G G F Y _ T -., T CX uX+ ,XH M AXH 2.4 . Y X @.PXk T 1X . J 3X] F 1.eX5X* : X N { <X| S S G S G S S S S S S S S S S G C C C m m m m b m b m j b b b j j j b j j j j j j <X@X` | X.| | ' _ ] | <XwXrXVXPXHXVXeXX.o.,X,X .} { .<X2XyXVXrXCXmXhXhX'.I.Z.A.J.V.J.c.y.y.i.c.c.c.y.p.t.u.m.m.v.y.9.8.9.8.8.9.r.c.M.u.u.M.J.F.F.F.G.F.-X,.>.;.dX=X^.S.F.F.F.m.c.e.7.9.u.m.V.m.y.8.p 7.7.8.f m Z n Z Z n Z k A b : r : : : r ; UXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXC m G Y Y Y T Y { 2 T AXo <.K 8X' + fX' 1.2. o H * ..KXB B 9X b 3X_ b ,XPXPXP ; , G G S S S S S S S G S S G S S S S C C M m C m m b m j b j b b b b b j j j j j j j k #XyX` ' _ ] | 4XrXrXHXPXVXVXeX<XOX] o. .@...1X8XtXVXVXAXgX5.AX-X^.D.A.A.A.A.F.Z.Z.M.c.n.m.J.A.J.m.y.8.9.e.9.8.7.7.7.9.r.u.c.V.C.C.C.n.v.M.V.J.F.F.F.Q.%X>X>.>.;.dX$XG.S.F.F.F.C.c.e.u.m.J.C.v.e.p p 7.7.8.e.c m n Z Z M Z Z C Z m : 2 2 2 : : , UXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXC N C Y S Y / Y -.N 1XAX# 3.1. eX-. + fX . V 8X o _ ; ..PX4 ; uX* B 3X_ , 4XCX2.8X, k G S S S G S S G S S S S S S S C C M C m m k m b m b b b b j r b j j : j j j j b #XyX2X3XVXNXAXCXVXtX1X|.' _ ] | ,X8X8XrXNXNXSXkXzX>XgX:.>.:.5.=XQ.F.G.K.G.H.S.V.N.V.F.F.F.H.m.u.9.7.7.7.7.p p 8.8.8.y.u.u.r.e.9.9.8.r.u.v.m.C.F.F.S.G.$X>X,.:.,.BX~.F.S.F.F.F.J.c.c.V.V.V.n.u.9.7.p a p 7.9.y m Z A k m n Z n m k : r 2 r > j , UXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXm m S Y G H / _ -.J 1XfXj 3 2. 3X1. * fX| + uX . Y 2 | AXY X 3XN F 3X' , 3XfX1 fXV k S S S S G S S S S S S S S S S Z m m S m m m m 2 m b b b m b 2 j b 2 b j j j b r yXyXrXCXCXtX<X| _ _ | 4X8XtXVXrXCXvXjXjX'.I.F.A.Q.=XgX;.,.;.gX-X$XQ.F.F.F.F.J.n.M.J.C.v.y.r.e.y.v.v.c.c.c.c.u.y.r.8.8.7.p p 7.9.c.V.C.m.M.F.J.F.F.S.G.%XBX:.>.:.BX=X!.S.F.F.V.r.u.n.C.C.C.m.c.8.p i 7.9.e.r.x m Z M Z n n Z Z Z k 2 r : j : j & UXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXj M M Y Y Y _ _ _ 1.fX3X` O 0X -.2X * uX| X uX+ ; <X: ' VXT X <.@. . J ,X' * eXeX tX' 2 S S S S S G S G S G S S S S S m S m m m m m m m 2 m 2 k 2 k b b j j 2 j j : j j z.#X) ] ^ | 4X8XVXVXVXFXbXyXoXW.B.J.A.A.A.S.F.G.Q.-X5.;.>.;.5.;X:X$XF.F.F.H.m.m.m.c.r.e.u.m.V.F.V.c.u.e.8.9.7.7.7.7.8.8.y.M.V.F.V.m.u.u.u.M.J.F.F.D.^.:XBX:.>.>.BX=XQ.S.F.F.C.m.c.e.r.u.c.r.8.7.7.7.8.7.7.0.y k M Z Z m Z n Z C j j : : 2 > j X UXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX2 M C G H / Y _ / 1XVX .{ uX F 3X * uX{ * 1Xb . J tX; G fX' O T 0X . _ Y * * 8XtX 1X0X 2 G S S S S G S S S S S S S S S S S S m m m m 2 m b b b b b 2 m j j j j r j j b N z..XwXCXVXVXCXyXoXz.a.0.q.t.t.N.J.F.K.G.G.F.F.S.$X:X5.;.,.;.6.=XQ.D.F.F.F.F.F.F.F.V.C.H.F.J.M.c.y.9.8.7.p 7.9.y.u.c.c.c.M.v.c.u.u.N.M.V.M.C.F.F.F.S.^.=XdX;.>.,.>X%XF.S.F.S.C.c.e.y.M.n.M.m.m.v.e.p i p 7.0.A M n m k C k Z Z Z j : 2 r : : 2 UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX2 m C Y J Y Y G G { <XeXtX. 5X+ m 8X + uXX.j 5XT F tX; : rXo.; , 8X Y M & pXrX 3.tX . 2 S C S S S S S G S S S S S S S S S m m m m m m 2 m b b b b 2 k j j j r b 2 j j N z.z.#X.X}.w.8.t.t.t.t.e.0.0.i.b.C.F.S.S.S.F.Q.^.=X;X5.;.,.;.gX%XQ.H.H.F.S.F.H.V.V.F.V.M.c.y.9.q.e.e.e.r.u.u.r.r.r.8.9.e.r.v.V.F.F.F.J.m.V.F.F.F.F.D.Z.%XBX:.>.,.>XQ.G.F.F.H.m.m.C.M.v.V.J.J.V.u.8.p p 7.7.0.Z m Z Z m n M n Z n j r r 2 r > 2 UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX, C m Y G S G Y G Y X { CX-.fX3 r tX + uX| K @.2. B tX: 1 tX' N X fXo . J F . VXCX j fX 2 S C S S S S C S S S S S C S m m S m S m m k m m b b b b k 2 k j j b b 2 j j 2 j x.l.s.p q.r.c.b.B.b.r.e.9.p.N.N.C.F.F.F.F.F.F.D.~.;X5.;.>.;.6.:X%XQ.F.F.F.F.F.F.C.M.u.u.v.m.J.C.m.M.c.y.e.8.8.9.y.u.y.r.c.V.H.F.F.M.m.M.m.F.F.F.F.S.G.%XBX:.:.,.-XQ.K.F.F.F.F.J.y.r.n.V.V.n.c.r.9.7.7.8.p.0.n A m k Z Z Z n Z n j j 2 : : > > UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXS m G Y G Y Y G / O N eX2 uXrX| eXo O uX] H 3 1X , wXM N rX_ Y uX; . N B CXCX X ZX+ , G C S S S S S S S S G S C S S S m m S m m m m 2 b b b 2 m b b b j : 2 j j j b j x.x.w.0.9.e.r.u.N.V.J.F.F.J.m.m.C.F.J.F.F.F.F.~.=X;X,.:.>.:.6.:X&XS.F.F.F.F.C.M.c.c.V.H.F.H.V.M.c.y.r.y.v.V.J.V.m.v.u.c.m.C.C.J.n.e.9.r.M.V.F.F.F.D.G.%XdX;.:.4.-XQ.F.F.F.F.C.u.r.u.M.M.n.n.u.e.8.p 7.p 7.f Z Z n A Z A Z Z Z Z j : : : : r > UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXC C J J Y F G F / * 2 rX. 3 tX,XCX_ O fXY [ ; + 9X O 3X_ N rXK ` 1X4 . b N rXCXX 8X2 - G C S S S S S S S S S S S C S S S m m m m m b m m b b m 2 2 b b r b 2 j j r j j z.x.w.7.9.u.m.H.F.C.m.c.y.p.c.c.v.C.H.F.F.F.F.^.$X;X4.;.,.;.dX=XQ.S.F.F.F.F.C.J.C.C.m.M.M.v.m.m.m.u.y.c.c.u.y.y.c.c.c.v.M.n.M.v.v.c.M.c.y.y.m.H.F.S.G.%XdX;.:.4.;X%XQ.S.F.F.C.J.v.c.V.H.V.v.y.e.8.7.8.8.9.w.N k Z m Z Z Z M Z n j : : 2 r j ; UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXC m S J J F Y F Y , & eX, + 5X* rXwX1XfXV | -.. uX. . tX| K 1XN | 5X@. . X ,XN tXCX; 0XX. . - S C S S S S S S S S S S S S m S m m m m m k m m 2 k m 2 m 2 b j j : b r j j j j l.(.w.7.r.r.r.8.7.7.9.p.n.V.J.C.V.F.F.F.F.F.F.F.$X;X5.;.,.;.6.=X-XQ.F.F.F.F.F.J.M.v.m.V.F.F.F.C.c.y.y.y.y.r.r.9.8.7.7.9.r.y.v.v.m.n.N.v.m.J.F.F.D.H.Q.=XgX;.>.4.;X$XG.F.F.F.F.C.c.y.r.e.r.u.r.8.7.e.r.8.7.f m Z n Z n k Z k Z k r 2 : : : 2 , UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXM m F F S G F G G ; X eXm uX, 8XT J AX3X3X3X 0X, eX} K <X3 .. T 5X ; rXB eXVX1 <.0X . & G C C C C S S S S S S S S S C C m C m m b m j m 2 b b 2 b b j j j j : j j j j h x.l.w.p p p p e.n.H.S.J.m.M.v.V.S.F.H.F.H.S.F.F.Q.-X5.;.,.;.6.:X%XF.F.F.F.F.F.J.F.F.F.F.F.F.H.C.m.c.u.u.r.9.7.7.7.7.7.p 7.8.9.8.8.9.y.u.M.F.F.F.F.G.~.-X5.:.:.4.-XQ.F.F.F.F.F.V.c.u.v.m.C.v.9.8.8.8.7.7.e.f V Z m Z A n Z Z Z k 2 r 2 : : 2 - UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXM m S S F J J S G j 8XY 5X3 3X/ . fX[ rXCX* 1X4 2X1. K 1X; ,X j 1X * tX_ 8XrXN H wX . & S C C C S S S S S S S S S Z C C C m m m m m m k m b b k b j j j j j j j j j j r x.l.f a 8.e.u.m.V.M.u.u.r.c.m.V.m.m.m.H.F.F.D.Q.%X;X4.;.,.;.dX=X^.~.D.F.F.F.V.m.V.J.F.F.H.m.c.e.8.9.9.e.e.e.e.e.9.8.7.e.e.e.r.u.u.e.9.e.C.F.F.F.F.F.$X;X4.:.:.5.-XG.F.F.F.F.J.m.m.J.m.n.u.y.e.8.7.7.7.8.9.f M l m n m Z n M Z m 2 : r r > j * UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXj m M F F G S G F k . 3X| 2.T k.T o uX; | VX+XfX5X ,X1X J 1X< 4X + fX. * rXo. tXtXN b fX & C m S C S S S S S S m S Z Z S C m m m m m m j m m k b b j j j j j j j 2 j j j j x.x.s.8.e.u.u.r.r.y.y.v.n.c.u.y.M.J.F.F.F.F.F.G.~.;XgX;.,.;.6.:X-X~.D.F.F.F.F.F.H.J.C.v.r.e.e.r.e.8.7.7.i p p 7.8.e.r.y.c.M.V.V.M.y.9.u.F.F.F.F.F.F.~.;X,.>.;.gX=XQ.F.F.F.F.C.m.n.v.v.M.M.c.r.8.8.8.8.9.y.f b k m n n Z Z n Z j : 2 : : : j & UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXk C m J J F J S S M X.,X T <.| | . uX1 ' 3X& 1.CX .3X3X J 1X. <X. uX+ , tXX. rXrXN * ZXO & S S S C S S S S S S S S S C C S C m m m m m m b 2 b b j b j b 2 b 2 j j j j j r x.l.w.7.7.p i p 7.9.y.r.8.p i e.C.F.F.F.F.F.F.^.%X:X5.;.>.;.6.:X-XD.D.F.F.m.n.m.V.v.u.r.u.c.c.u.e.8.7.p a i p 7.8.r.c.m.J.F.J.F.J.V.v.M.F.F.F.F.F.F.~.;X4.>.;.dX%XQ.S.F.C.V.V.m.n.n.v.v.c.c.c.u.e.7.7.i g r M Z Z Z Z n M Z n k : : j j > 2 X UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX2 M C F F J S C C N . o X. 1 5XG , O uX, T wXX + 9X_ rXVX& V 1X <X& 8XM : 3X,X fXrXM fX> X S C S m S m S S S S m C C C C C m m m m b m b m j b b k 2 b b 2 j j r j j b r r x.l.w.i g p e.c.V.C.C.c.e.7.7.e.m.S.F.F.F.F.Q.~.%X;X4.:.>.;.dX:X&XS.F.F.F.F.J.m.u.r.u.c.n.M.v.n.M.c.e.9.8.7.8.7.8.e.M.C.C.F.V.v.y.y.y.y.v.C.F.F.F.F.$X;X4.:.;.dXQ.D.S.F.F.F.m.M.r.r.M.m.m.v.u.0.p i i 7.e.l Z Z M Z Z Z m Z m j 2 : : j > 2 UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX; M m F J J C C C m X ' . O uXG O uX, / 8X> . uXO ] eX4X8X1X ,X> 2.@. N ..,X ZXrXJ o rXT . X S C S S S S S S S S S S S C Z m S C m m m b b j b b b b b 2 b j b j j 2 b r r : x.x.w.p e.c.m.V.v.y.9.7.7.9.y.v.S.F.F.F.F.F.F.F.$X;X5.:.,.:.dX=XQ.S.F.F.F.F.V.u.9.8.e.c.m.J.Z.V.n.c.c.c.u.u.r.e.e.r.u.c.v.y.y.u.c.M.c.e.y.F.F.F.S.F.Q.-X4.:.;.dX%XH.S.F.F.F.m.c.c.y.e.7.a p p 7.7.r.b.M.m.x n Z n k Z Z k n m b r : : : : r UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXM m F F F S S M M & H * uXX. O uX, H eXJ 0X> J * * 4XVX' <X3 @.5X H _ ; AX8X_ O uX2. . X C m S S S S S S S S S S m Z C C C m m m m m b m j j j j j b 2 b : j j b : : h 1 x.(.}.7.8.9.e.e.y.v.V.C.C.V.H.F.F.F.F.F.F.F.F.Q.$X;X4.;.>.;.dX%XQ.S.F.F.F.H.F.C.v.u.v.m.V.m.V.C.J.J.H.C.N.r.8.8.7.8.e.y.v.m.V.H.C.V.M.c.c.C.F.F.S.G.Q.-X,.>.:.BX~.G.S.F.F.F.H.c.e.7.7.9.c.v.v.r.7.i p 8.0.Z n k n Z m M Z Z Z r 2 r j : : > UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXM m F G F F S m m * b ; 5X,X + uX+ M 3X' 1XV H , T tXY NX2X 3 5X J B . AX<Xo.* 1X3X X m S S m S m S S m S Z S C C C m C m m b m b m b b m b j k b b r b 2 j : b r 1 1 l.l.}.p 8.e.v.C.J.J.m.v.c.m.J.F.F.F.F.F.F.F.S.K.Q.-X5.:.>.:.BXQ.K.S.F.F.F.F.J.V.J.C.V.C.m.n.n.m.V.u.r.8.p p 7.7.7.q.y.n.C.J.F.J.V.v.r.y.v.J.F.F.F.Z.~.>X,.:.,.vXA.G.S.F.F.H.v.c.m.J.F.F.F.F.v.7.g p 7.7.p n n Z n Z n k D n m 2 : 2 : : > > UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXM k G J F F J M M - ; b 3.8X + uX+ b 3X| 2.3.Z ; X.8X 1.wX4X<XuX M K X gX, | & o.9X . M C C C A C C S Z C C S Z Z C C m m m m m m k b b j b j j j j j 2 j j j b 2 k 1 x.(.w.0.b.n.m.M.v.c.y.y.c.v.v.V.m.C.F.F.F.F.S.G.~.-X4.;.>.:.BX=X$XD.F.F.F.F.F.F.F.F.F.J.J.v.c.u.r.r.e.9.r.e.9.8.8.8.8.8.9.e.r.9.7.8.y.m.J.F.F.F.S.G.$X>X,.:.,.vXQ.K.S.F.F.F.F.J.v.m.C.J.M.u.9.9.9.7.p 7.p M Z k C Z M n k n Z j 2 r : r > ; UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXk m S G G G J S k ; , K [ fX + uX+ b 3X-. T 1X: ; 3.8X ' H ; <XAX' J F < fX ' * H gXO m M C C C S A S Z C C C C S C C Z m m m m b m m j b b j j b j j j 2 j j 2 2 b 1 x.(.w.u.u.c.M.v.n.M.v.c.y.y.e.r.v.V.C.F.F.F.F.Q.=X;X4.;.>.;.dX=X^.S.F.F.F.F.F.J.C.V.m.M.m.n.m.m.C.H.J.V.n.n.u.e.8.p g i p 7.p 8.r.c.v.c.M.F.F.F.S.F.~.BX>.:.>.BX~.G.S.F.F.F.m.8.8.c.c.c.y.y.r.9.8.8.e.y.w.n Z k m m k Z M n n > : : : : j ; UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe k S F C J D M M > O H 1XfX + uXo 2 1X-. 1 1XB ; @.5X S ,XF rX3XeX3X. , fX Y , N uX, . m C Z S Z C C S C C C Z C Z m C m m m m b m b j b b b k 2 j k j b 2 j j j 2 k > l.x.}.8.9.e.r.r.r.p.r.r.9.e.e.r.u.m.F.F.F.F.F.^.%X;X4.;.,.;.dX:X&XF.F.F.F.H.C.m.v.u.r.r.u.c.n.V.M.u.u.r.9.9.p i i 7.7.8.q.8.u.M.c.c.c.v.V.F.F.F.S.G.$XBX>.:.,.>X$XG.F.F.F.J.c.c.v.v.c.M.c.y.e.9.9.9.7.q.s m Z S k Z m M Z m k j r : : > 2 , UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXw w k S S J F m Z > X ] 1XZX. + uX b ` | + uX] ; @.5X 2 3X4X 5XB F rX4XH : aX H ; ; 8XK . m C M Z Z C C S Z C S C C C S m Z m m m m m b m b j b b b b 2 j 2 k j 2 j 2 k 1 x.(.f p 7.8.e.r.e.e.r.r.u.y.r.u.C.F.F.F.F.F.F.Q.=X;X,.>.>.;.dX=XQ.S.F.F.F.F.F.F.V.m.V.C.C.C.M.r.7.a p i i i p 7.8.8.8.8.e.v.m.v.v.v.c.u.y.v.J.F.A.^.:X6.;.>.,.-X~.F.D.F.F.H.H.C.m.J.J.C.C.M.u.8.p i i g t Z Z S A Z A Z m M m 2 : 2 r > j * UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq w t t l A C M M r . ] T CX, X uX C X J X 8XX., @.2. , 8X3X 5X3.. | r 4XwXfX M M , 8X@. . j S S S C Z S C C Z Z Z C C m m m m m m m b b b b j j j b k 2 j 2 j j j j j j r l.x.w.p 7.7.p 7.9.p.N.M.v.v.y.n.S.F.F.F.F.F.D.^.=X;X:.;.:.;.dX=XQ.S.F.F.F.F.F.F.F.F.H.C.n.u.e.8.9.8.7.p 8.e.r.r.e.e.r.r.c.c.r.e.7.7.q.9.v.J.F.F.Q.&X=XdX:.:.4.-XS.F.F.F.F.J.C.m.m.m.V.m.c.9.7.p 7.7.p 7.f N n m A S Z Z n M k > > j > > j & UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX8 w e y y y y t k k K ] / fX3 X uX. S K , 8X1.* 1.5X X 3X3X [ <.. ' X _ CX,X,XH * <.5X . 2 S m C C C C S C C S C C C C C m m m m b m b b b b j b b j j j j j j j j 2 k 1 x.(.w.i g 7.r.v.J.J.J.V.C.V.C.F.J.F.F.F.F.F.F.H.$X;X4.:.>.;.BX%XP.S.F.F.F.F.F.m.v.r.e.8.9.e.r.r.9.7.8.9.r.r.r.u.y.e.e.8.7.p 7.r.v.m.H.F.F.F.F.S.S.H.$XdX:.:.4.-X~.F.F.F.F.F.V.u.u.c.r.9.8.e.r.8.7.7.8.8.f Z Z m k C Z Z m m k j j 2 j > j X UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX$ w e y t y y t e 8 . : 4X} fXT . uX C . C N eXaX& 1.2. o 3X3X N 1X. ] > tXN eXtXC ; P 3X . 2 S m C Z C C S Z S m C C C Z C m m m b m b b b b b j 2 b j j j j j j j j 2 m 1 l.(.w.7.u.m.V.J.C.H.F.F.F.F.F.F.C.H.F.F.F.F.F.Q.=X;X,.:.>.:.dX:X^.S.F.F.F.F.C.v.u.r.e.r.u.u.e.e.e.e.u.u.y.e.7.p i i p 7.r.m.F.F.H.m.c.m.H.F.F.F.S.G.=XdX;.>.4.;X~.F.S.F.F.C.c.u.u.u.y.c.M.u.8.p i i g p l h M S A Z C Z k k j > : r 2 > 2 UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX% e w y y y e y e 6 @ N <X| rX-.X fX N X b J 8XCX* <.2. 2X8X * uX; ' b 9X m 8X4XwX .uX 2 S Z C C C Z m S C S C m m C m m m m m m m b b k b b b j j j j j 2 j j j 2 k 1 h.{.0.q.e.u.m.C.C.C.V.n.M.v.c.m.J.F.F.F.F.F.F.Q.=X>X,.;.>.:.BX=XG.S.F.F.F.F.F.H.J.J.F.J.J.C.F.J.C.n.r.p i g g p e.v.m.F.F.F.C.c.8.7.7.7.u.H.F.F.F.Q.=XgX;.;.5.-XQ.F.F.F.H.H.V.M.M.m.C.m.u.9.8.p a p 8.0.x C A Z S A k C Z n k j : 2 r : : UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe w l y y e t e w o b ,X-.8X0X: 8X b O , _ ,XCX> <.1. -.tX . fXJ ] V 8X , ' X ..eXAXS . > S M S C C m C C Z C C Z m Z C m m m m m 2 k b 2 m 2 j j j j j j j j j j j b 2 l.(.a.c.m.Z.V.M.M.c.u.y.c.M.J.F.F.F.F.F.F.F.S.Q.=X;X4.:.>.,.>XQ.F.F.F.F.F.F.F.C.J.F.F.F.H.n.c.r.7.p i i 8.r.u.u.M.m.V.C.m.c.c.r.e.8.8.v.F.F.F.F.D.Q.=X5.;.;.gX;XQ.F.F.F.F.C.y.p.u.p.y.r.e.e.7.7.a a s 7 l Z k Z C Z m k k m k : 2 : > j > UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXt q y y y t e t w @ r rX-.4XtXT 3X : * X _ _ AX3 2.1. X.rXO eX3XH O Y 8X O { ; j fXtX<Xo.X . > C Z m Z C Z C C Z C C C C C m C m m b m m m b b k m b b k 2 2 j j j j j 2 j 2 x.l.a.y.9.9.e.y.M.C.H.J.C.m.v.M.H.F.F.F.F.F.F.D.%X>X,.:.>.>.BX$XG.F.F.F.F.F.J.F.H.m.c.r.9.9.r.u.c.c.c.c.u.8.p 7.8.r.r.8.8.e.y.u.u.r.r.y.n.F.F.D.G.Q.-X5.;.;.5.-XD.F.S.F.C.y.8.8.q.p.c.M.v.9.p g 6 w v ( } ( ( S Z m Z S Z S 2 r > j > 2 = UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe q y y t t y t e @ X o.' 4X9XfX1X ; ; . H N AXJ 2.1. H rX> 8XrXF < T eX o ' > O 1XY <XPXrXo.o . , S M C C Z C C Z C C Z m C C C m m m k m m b b b 2 m j j j k j j j j j 2 b k 1 h.(.}.7.r.v.M.m.m.n.c.e.8.q.u.C.F.F.F.F.F.F.S.G.%X>X,.:.>.>.BX=X^.F.F.F.F.F.J.u.7.a p r.m.F.F.F.C.M.u.e.7.p p p i p p e.M.C.J.V.m.c.r.y.V.F.F.F.F.Q.-X5.>.:.gX=XD.H.F.F.V.u.r.p.c.N.c.e.7.s d x Q j.} } } } } } ( D Z m m Z 2 : : r ; 8 % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe q y y l y y y w W ; . _ wX3.PX@. * > Y b HXX. 0X@. X ,XN T rXH O T 8X . ] , o 5X1.` { 3XAXNX] o . . , C m C C m C m C C Z C C m m m m m m m m b b b b b 2 j b j 2 j j j 2 j j 2 k 1 x.(.a.M.M.v.c.v.M.v.m.V.M.m.C.M.r.y.M.F.F.F.F.H.%X>X,.:.>.:.BX=X$XS.F.F.F.F.m.c.c.M.C.F.F.J.C.C.n.v.y.8.7.p a a i 7.r.u.v.v.n.m.m.v.n.F.F.F.F.F.F.$X;X4.>.:.dX$XF.F.F.F.F.V.c.t.7.s s c Q ! ) ) j.} j.} } } } } } } } ( G m k r > 8 $ $ % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXw w l l y y w e w y X._ . K 3XM P ; X j ] , CX2. 8X4 . | J 2 VX_ O 1.5X ] X < X @.0Xo.< A tXHXrXJ + . , C C C M m C C C C C m C m Z m C m m m b b m b b m 2 b k b 2 j j j j j j 2 j 1 h.(.0.0.r.u.M.n.J.H.F.F.V.M.e.q.8.m.F.F.F.F.F.F.$X>X4.;.>.:.BX~.G.S.F.F.F.F.F.F.C.v.v.v.m.m.C.n.y.7.p 7.8.8.8.p p 7.8.e.c.m.C.m.y.u.m.J.H.F.F.F.F.Q.;X4.;.;.dX%XP.S.S.D.V.i.w.v d.h.h.h.j.j.) ) } ) } } } } ( } } } } } ( k q $ $ 5 $ = % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q e l y y y e e % . _ o.' _ . . m ' X & rX8X 8XB . _ F + ZXX.* <.5X ] * X , 2 tXJ o . B rXLXfX1.+ , S m C m Z C C Z m C Z m C m m m m m b m b m b b 2 k b b k 2 j 2 j j j j j k j }.x.}.7.7.8.r.c.c.M.c.c.u.r.y.v.V.F.F.F.F.F.S.Q.%X>X,.:.>.,.>X$XQ.D.F.F.F.F.F.V.m.n.M.M.c.c.e.8.8.e.u.u.r.9.7.7.8.9.u.V.V.c.r.8.e.r.u.C.F.F.F.F.F.D.-X4.>.>.dX$XG.S.G.Y.a.}.h.h.l.h.h.j.! j.j.j.) ) } ) } ) } } } G l q % $ q 8 $ $ $ = @ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq w t l l l y t t $ ; | ' X . K ] |.O eXCX 8X3 . J B X CX,X+ <.2. ] r . ; O fXX.. . 3 PXPXKXAXX.m C M M M M C C Z C C C Z C m m m b m m m 2 b k 2 b 2 k 2 b j j j j j j 2 j 2 h.x.f g g i 8.r.c.v.c.u.c.m.V.J.F.F.F.F.F.F.S.F.%X>X:.>.:.,.;X=X~.S.F.F.F.F.F.H.v.r.r.u.y.e.r.u.u.u.8.8.9.9.e.r.y.c.M.v.u.c.n.n.u.y.N.V.H.F.F.S.G.$X;X,.;.:.BXQ.I.!.T./.l.l.h.l.h.h.j.j.j.j.) ) ) ) } } ( ( D e q $ % $ q $ $ 5 $ $ $ = @ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq e e l z l l e e 8 . J ..b B J tXj <XAX 3X: J H tX3X* 0X2. M J . N gX3X aXtXO 1.PXAXC C M m m C m C m C C m C m C m m m m 2 k m b b b b b 2 b 2 j j j 2 2 j j k 2 j.z.}.g g 7.b.c.u.r.r.r.y.v.c.c.M.M.m.J.F.F.S.&X:XBX:.>.:.,.>X%XQ.F.F.F.F.F.V.u.u.m.C.n.c.n.v.c.r.u.c.u.e.q.7.9.r.y.y.u.M.u.e.p i 7.e.c.H.F.F.F.G.D.>X4.4.gXhX].'.'.(./.x.h.l.j.l.j.! j.j.) ) } ) ) ( l w 5 % $ $ 8 q $ $ $ $ $ $ 5 # $ o UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX% w e y x x l y t 9 * | | * C . j 3X' { fX4 uX; K ' rXrX+ 0X1. O | H S eXwX. o AX3 + | S m C m C m C C Z C C m Z C m m b m b m m b b b b j j j j j j j j j j j : b h ^ (.w.8.y.u.y.u.c.v.m.V.n.v.r.p p 8.V.F.F.F.Q.&X=XBX>.>.:.,.>X%XG.S.F.F.F.H.C.H.C.n.M.n.V.m.n.M.n.v.r.p i p p 7.r.y.p.r.9.p p 8.r.c.V.G.F.D.S.K.!.hXPXgXKXHXjX[.].'./.l.l.x.l.h.j.j.j.j.j.j.( D l q $ # # 8 8 8 = 5 = # = = = 5 $ $ $ = UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe e R y l y y y w o O ,XX.C 2 ; 3Xo.1.8XtXfXo M ' O 0XfXo 1X@. . <XVXJ 5XtX ; fX. X m Z m C m S Z C m C C C m m M m m m m b b m b b j j j j j j j 2 j j j j j b > h.(.w.e.9.e.r.u.y.y.y.e.9.8.7.8.M.F.F.F.F.F.S.G.%X>X>.>.;.4.;X~.H.F.F.F.F.F.C.u.r.c.V.C.m.V.J.V.c.e.9.8.8.8.9.e.e.e.9.9.y.v.n.M.V.m.c.c.M.P.'.jXDXKXPXPXJXcXXX[.]._.).x.x.x.j.x.j.j.j.! R e 8 5 $ $ 8 8 8 8 5 = 8 = 8 8 8 = = $ $ $ $ = UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe w y x l z l y t @ . ,X> ] |.* O 1X' 2XOXAXtX . b | < <XVXo 8XT X X.VXK 1.fX . T tX . O C m C m m m m m C m m C m m m m m m m j b b b j j b b j j j j j j j j r r b 1 h.z.w.i i i i p 8.e.c.n.u.c.n.F.F.F.F.F.F.F.S.^.=XBX,.:.:.>.;X=XQ.F.F.F.F.F.C.F.J.m.M.n.V.m.M.y.e.y.9.p g g i p p 8.r.y.y.r.r.r.r.i.f.(.[.jXzXDXKXLXPXJXSXlXXX[.{.).z.).l.j.j.j.~ l e 8 - $ 8 8 8 = $ = 5 8 8 8 = = = = = = = $ $ $ $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXt w l l l l l y y @ nX< n ..] { ' 3X,XV P : ] | o X.PX* 8XJ . o ' Y P VX . 5X9X . o m m C C C m m C C m m Z C C M m m k m m m b b j b j j j j j 2 r j j j j j b > h.x.s.@ g i 8.c.H.F.C.V.v.m.F.H.S.F.F.F.F.F.S.Q.=XBX:.>.:.:.>X:X~.F.F.F.F.F.F.C.r.7.9.u.c.m.n.n.u.8.i i p 8.e.y.r.e.8.q.7.0.a.}.(.).].XXjXmXSXHXLXLXLXFXzXhXjX_._._.z.j.h.Q v 0 = = = 8 8 8 8 5 $ 8 8 = 5 = = = 5 5 = = = = = $ = $ $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe w l l R l R R z l O . tX_ . o _ <X' ] ' o 1 S eXj B AXj 9X3 M ' ; CX. . 8X<. . o C m m m C S C Z m m M m C m m M k m m b b b n j j j j j j 2 b j 2 j j r j j 1 h.(.}.i 9.v.C.V.n.c.u.c.u.r.c.m.C.C.F.F.F.F.S.P.%XBX:.>.:.,.;X=X~.S.F.F.F.F.J.v.u.c.n.m.V.M.u.r.9.0.e.y.r.e.e.7.p p f }.}.l.l.(._.[.jXhXxXSXDXGXJXGXSXzXlXjX*X_.z.j.c y 5 % $ # 0 8 = 5 = = 8 8 = $ 8 = 8 = 5 0 = = = = = = 5 $ 5 $ $ % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXw w l z R R R ~ x 4X1. ; o nX|. . S |.' * > M 3X_ : CXT fX; N .. X AXO . rXP . m m Z C m m m C m C m m m m m m m b m b b b b b b j b 2 j j j j j j j j j b > h.z.}.M.V.n.c.u.u.c.v.v.n.V.C.n.V.F.F.F.F.F.S.G.%XBX>.>.:.,.;X%XQ.S.F.F.F.F.F.m.v.u.y.r.e.e.r.y.9.8.7.7.7.p s.w.}.}.l.l.l.(._._.[.jXhXzXmXFXFXGXSXFXvXlXjX_.j.Q t 5 % q q q $ 8 = 5 5 = = 5 = 8 = 8 = 8 = 5 = = = = = = # = 5 $ 5 $ = % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX0 8 y R z Q x ~ c wXpX. .N aX4X . , , J ,Xj > B 8Xo.: fX1X AXO b ,X aX; fX> . m m m m C m m Z C C m C Z m m m m m b n n b j b j j r b j j j j j j r b 1 j 1 h.(.a.e.e.r.y.v.M.C.J.H.V.v.y.m.F.F.F.F.F.F.F.Q.%XBX,.>.;.4.-X~.G.F.F.F.F.F.C.c.9.u.m.m.C.V.y.8.p i s s.w.}.h.}.h.l.l.x.z.oXoX[.jXkXkXcXcXmXFXFXSXvXoXk.c 9 % % 5 q q q $ 5 5 8 = 5 = 8 = = 5 $ = 8 8 = 5 = = = = 9 # = = = = $ $ 5 8 & UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX8 0 y R l l R ~ z ,X0X. iX7X PXqX , ; ; X.{ O . , M 3X| N rXeXX fX. ; 4Xo 8XV O AX+ m C C m m m C m C m M m M m m m m b n b b b n b j b j j j j j 2 j j j j j 1 1 }.f.w.7.7.8.e.c.V.J.V.c.r.e.r.v.C.F.F.F.F.F.D.^.%XBX>.>.;.4.;X~.F.F.F.F.F.V.m.n.C.C.m.n.u.0.7.p f }.}.h.h.}.}.x.l.x.x._._.oXjXjXlXzXvXmXcXmXvX.Xk.A 6 % % $ q 9 5 $ $ $ $ $ $ = 8 = 8 = 8 8 = 8 8 $ 8 = = 5 = = = = = = = # 5 $ = $ 8 @ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX= q y x R R x ~ h.k.fX1 ZX X aXaX < ; . K ,XM , M 3XX.F eXZX3 tX * <X* eX2. , fX. j C m M m m m m M C m m m M m m b b b m b b b j j j j j j : j j j j j j : b ; ~ x.}.7.7.r.M.m.V.n.u.r.9.8.9.n.F.F.F.F.F.F.D.^.=XBX;.,.;.4.;X~.F.F.F.F.F.C.m.m.b.M.i.i.0.}.}.}.c h.h.}.h.l.l.l.z.z._._._.oXoXlXzXxXvXbX#Xj.e # @ # 8 0 0 = = $ 8 $ 8 8 = 8 $ 8 = 8 = 8 = = 8 = = = = = = = 5 6 = = = = = = $ $ $ $ = @ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX= w e R R R R ~ ! ) AX|. PXnX. 7XMXo O ; * | .& X K 3X' _ 2XVXAX8X . O ..' 1XeX 2 tX j Z M m m C C m Z m m C m m m m m m m b b b b j j b j j j j j r j 2 j j r b 1 h.W.}.9.u.v.M.m.m.M.M.v.u.u.u.n.C.F.F.F.F.F.S.^.:XdX;.>.;.4.;X~.G.F.D.F.H.m.v.M.B.i.f.}.l.}.}.h.h.h.h.h.h.l.z.z._.z._.oXbXjXlXlX_.j.Q 5 % & 5 q 0 8 5 $ = = 8 5 8 8 $ = 8 8 8 = 8 = 5 = 8 5 = = 9 = = = = = = # = = = = = = 5 = $ # 5 o UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX% w e z x x R ~ ! W ZXVX_ ZXpX <.MXX o ; F |._ ; 4X2XY ,X5XPXT X ] eX, X.fX Y 8X . j M m m m C m M m C m m m m m m m m m b b b b b j b j j j j 2 j j j j j j b r h.l.}.8.r.c.m.C.J.J.V.M.N.u.u.v.J.F.F.F.F.F.S.^.=XBX;.>.:.4.;X%XF.D.D.Z.Y.B.f.a.l.}.l.l.}.l.l.}.h.h.l.l.z.z.l.z.z.).oX_._.z.! t $ % % 8 q q = $ $ $ 8 5 8 = 8 = $ 8 8 8 8 = 8 5 = = 8 5 5 = = = = 5 = 9 = 5 = # # = = 5 5 $ $ $ $ # 8 UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe e l x R R R ! ) sX X| ZXZX; 7XLX, . . : O ' -.| o.' -.F L + o H tXN Y ZXX 1.8X . 2 M m C m m m C m m C m m M m m m j b m b b b b j j j j j j j j j j j j r b 1 R (.a.e.u.c.c.c.v.M.m.M.c.r.c.C.F.F.F.F.F.F.S.G.%XBX:.,.;.4.;XQ.G.L.U.T.W.f.f.f.l.f.f.}.l.}.}.h.h.h.l.h.x.l.z._.z.z.h.d 5 - % $ q w q 8 $ = = 8 8 = 8 = 5 5 = = 8 5 = = = 8 $ = 8 8 = = 8 5 = = = = = = = = = = 6 = 0 9 5 $ $ $ $ = = UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe w x l R ~ ~ ! j.pXnXK 5XGXD 1.ZX; . F j N ..' ] m . / tX_ N fX; 9X0X . 2 m k m m m C m m m m m m m m m m m b b b b b j b j j j j j j j 2 j j j : b : ~ l.w.7.7.8.e.u.m.m.N.u.y.u.v.m.V.F.S.F.F.F.F.^.%XBX;.>.:.gX-X`.`.`.T.W.f.f.l.f.l.}.}.h.l.l.h.h.h.x.x.x.z.x.h.v d $ % - = 8 q 8 5 $ $ $ 8 8 8 8 8 = 8 = 8 = 8 5 8 $ 8 8 = 8 = = 8 5 = = = = 5 6 5 8 8 5 5 5 5 6 0 5 5 0 5 $ 5 = $ $ # UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe 0 x x R R R ~ ! 9XMXP 2.nX@.rXPXU < 3X . j O _ ,XB _ rX| > fX@. tX3. . > C M M m C m m m C C m C m m m b b b m m b b r j j j j j 2 j j j j j j b r 1 R }.w.p 8.r.b.n.c.c.M.c.M.c.u.v.V.F.F.F.F.F.S.A.~.dX,.5.AXLX*X[.'.'.'.'.(.(.l.l.l.l.g.l.l.h.h.l.l.x.h.h.l 5 # % $ 8 q q 5 $ $ $ = 8 8 = 8 $ 8 8 = 8 = 8 = 8 = = = 5 = $ 8 5 8 8 = = = 5 9 8 - < 8 8 8 5 5 5 5 = 6 9 9 9 5 = = $ $ = % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe q l x x R R R ! 5XZX7X5XnXh @.LX@.O AX^ M : X.o.o ] tX] - uX5X fXN > Z m m m m M m M m m m m b m k m b b m b b b b b 2 r j 2 b j r 2 j 2 b 2 b 1 c }.}.9.r.y.y.u.u.u.u.u.u.r.u.n.S.F.S.S.S.F.!.*XHXAXgXPXLXSXjX[.{.'.`.(.(.l.f.l.x.}.l.h.l.h.x.d.x t = # # = 8 8 8 = $ $ = 8 8 8 8 $ = 8 8 $ = 8 $ $ 8 = 5 = 5 = 8 8 8 5 8 = = 5 9 0 = - * 5 s a.0.# 9 5 9 9 5 9 9 9 0 0 5 5 = $ $ $ % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq e y x R R ! R R 7XsX4X@.ZX@.@.ZXrX .KX X N . X Y <XM / tX' : .1XO AX+ , C m m C m m m m m M m m m m m m m b b b b b b j b j j j j j j b r b r b r 1 R }.w.8.8.8.8.e.r.c.M.M.u.r.r.u.V.J.K.U.'.[.cXLXKXAXPXLXDXzXjX].[.{.(.{.(.l.l.l.l.x.h.h.c R 0 # # $ = 5 8 8 = = $ 8 8 5 = 8 = 8 = 8 8 8 $ 8 5 = 8 = 8 8 = 8 = 8 5 = = = 8 8 8 8 - * = s i.Y.L.L.i.- 9 5 9 9 9 0 6 0 9 9 5 5 = 5 $ = % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXw q y z R z R ~ }.^ nXI T ZX X4 ZX XV PXZX 3XN , ..| ; . J eX| _ J aXT fX. , C b C m m m m m M C m m m m m b m b m b m 2 b b 2 2 j j 2 2 j j j j r j k 2 R }.f p p 7.9.y.M.v.r.8.7.0.0.b.W.'.[.[.hXmXGXKXKXKXLXGXFXkXjX[.[.{.)._.x.(.l.h.}.! t 9 # % 5 5 8 8 8 = = 5 5 8 8 = 8 8 = 8 8 = 8 = 8 8 = 8 = 8 8 = 8 = 8 8 $ = = 5 8 9 = - - 5 f i.Y.P.K.K.P.K.0.- 6 0 9 5 8 8 q q 6 q 5 $ $ $ $ 5 & UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX8 9 y l z R R R R h aX@.T MX@.@.ZXqXN AXMXY VX8X N X , O A 3X_ b wXo.' m [ PX3X . - S m m m m m M m m m m m m m m m b m m b 2 b b j b k j j j j j j j j 2 j j 2 l h.f 7.8.8.8.8.7.7.8.0.a.g.(.{.[.XXjXzXmXGXLXKXKXPXPXFXxXkXjX[.[.{._.(.l.}.x u = # = = 0 0 8 = $ 5 8 = 8 8 = = $ 8 8 $ 8 = $ 5 = 5 = = 8 = 8 8 = 5 8 = = = 8 8 5 = - - 8 s i.Y.K.K.K.L.P.K.P.K.a.= 9 5 6 0 9 9 9 q t w $ $ = $ $ = @ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX$ q y R l R R ! ! R 6X|.I sX7X| aXiX3 aXsX4XZXaX * 3XX 8X* , . 8X<X J ..X.; > 8Xo.' F + KX4 * C j m m m m m m m m m m m m k m b 2 m k b k b b k j j j j j j 2 j j j 2 j j h R d p i i i p 0.}.l.x.(._.{.[.jXhXzXxXFXGXJXLXLXGXGXcXzXjXXXjX_.(.}.d 0 = = = 8 5 8 8 = = = 8 8 = 8 = 8 = 8 8 = = = 8 = = 8 = 8 = 5 8 = = 8 5 = = = = 6 8 8 * - w p i.Y.K.K.K.K.K.K.K.K.K.K.P.s.= 6 5 9 9 9 u q t 9 9 5 5 $ $ $ = @ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX# e e R R x x R ~ l @.7X@.sX7X1.aXiX[ fXnXo.6XPX` J GXP tXK ] . qX7X h O N 4XH * eX,X_ P 3 . & S m m m m m m m m m m b m m m m b b m b m b 2 b 2 j j 2 j j j j j j j j j : R c l u i f }.}.l.}.(._._.XXXX[.hXzXzXmXFXGXGXGXGXGXFXxXkXXX_.h.d = - # 8 8 0 8 8 8 = = 8 8 8 8 $ 8 = 5 = 8 = = 5 5 8 = 8 8 = = 5 8 = 8 8 = = = = 8 9 = - - 6 w.b.Y.K.L.L.K.K.K.K.K.K.K.K.K.L.P.s.= 9 8 6 9 u 9 e w 9 8 5 $ $ $ $ 6 o UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX& e w x x R x R ! x |.sX[ 6XsX@.uXnX[ uXnX^ @.AXqX/ CXMXh FX|. X. . nXtX : , * ' ,X..| o.H o & m N m m m m m m m m m m m m m m b b b b 2 j b j j 2 j j 2 j j : j j j j j : A h.R c }.l.l.l.x.(.{.oX[.XXhXhXzXcXmXmXDXGXGXGXGXSXlX.Xh.t # % # 8 0 0 = = 8 = 8 5 8 8 5 8 5 $ 8 8 = 5 = = 8 8 = 5 = 8 = 8 8 = 8 = = = = = 9 0 = * # 9 s.a.Y.K.K.K.K.P.K.K.L.K.K.K.K.K.K.K.K.K.s 0 u 9 9 q q 9 9 9 9 8 5 5 $ $ $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXw w y R l R x ~ x XiX@.6X6X[ MXpX@.uXnXV 4 AX XK PXMX4X iXqX 8X1 o qXnX ; , b ..| H N + C m m b m m m m m m m m m b m m m 2 m b b b j b j k j j j j j j j j j j j 1 R ! Q l.h.l.x._.(._._.oXXXjXkXzXxXmXmXmXFXFXGXlX.XW 0 % < 5 8 0 8 5 = = 8 5 8 8 8 = = 8 = = 8 = 5 = 5 = 8 8 = = 8 = 8 = 8 5 = = = = 8 6 0 = * - u s.i.P.K.K.K.K.P.K.L.K.K.K.G.L.K.K.K.L.K.L.K.P.s 9 6 9 9 9 u t 9 9 w 8 $ 5 $ $ $ = UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe 8 y x y x R ~ f qXiX2.sX6X1XaXpX|.1XaXL V ZXnXT aXZX|.. VX7X 0X3. X aXiX ; ; O _ <XN X m m m m m m m m m m m m m m m b b m 2 m b j b k j j 2 j j j j j j j j r j > R h.j.l.l.(.z.z.{.oXoXXXXXjXkXkXxXmXmXmXqXk.A < % % = 9 9 8 = $ $ = 8 5 8 = 8 5 = = 8 = 5 = = 8 = 5 = 5 = 8 5 8 = 5 8 8 = = = 5 9 8 = , - u s.b.P.K.K.K.K.K.K.K.K.K.K.L.K.L.{.G.K.K.K.L.K.K.K.P.7 q w w 9 6 w t 9 9 w 8 5 $ $ $ $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe w l x R x x R x ^ aX6X7X6X7XaX6XqX6XaX@.P VXtX1.0XnXtX<XZXnXo fX0X o @.MX. & > 2 o.F . X C m m b m k m m m m m j m m b m b m k m b b j j j j j j j j j 2 j j : j j : A h.l.x.x.x.(._.oXoXXXoXhXkXkXkXlXoX^ t 5 @ # 0 e 9 5 $ $ = 8 8 8 = 8 5 = 5 = 8 = 8 = 8 = 5 8 = 8 = 8 = 8 = 8 5 = = = $ $ q 8 $ * = u s.b.P.K.L.L.P.K.K.K.K.L.L.K.K.K.L.S.jXJXK.I.K.K.K.K.K.K.P.7 t 7 w u 9 t 9 t w 9 8 $ = $ $ $ # UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe q h f x l R R R ^ <.pXpX6XaXpX7XqX5XMX|.I fXqX; 2.iX|.1 PXnX 5X0X o 4XiX O : * ,X| * X m m m m m m m m m 2 m m b m b m k b b j m j b j j j j j j j j : j j 2 j j j l h.x.l.)._._._._.*XjXjXXXoXk.c 5 @ % 5 9 0 0 5 # = = = 0 5 = 5 8 8 5 8 = 8 5 = 8 = 8 = 8 8 = = = 8 = 8 8 8 = = = 8 9 8 = - - 7 s.B.K.K.K.K.K.P.K.K.K.K.K.L.L.G.L.K.K.I.A.kXKXK.F.L.K.L.K.K.K.Y.u u 9 q q t 9 9 w 9 9 q $ $ $ $ = % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q d R l x f ~ f k.@.7XpXiXpX6X6X@.<.sX#.V aXsXO 2.iX|., uXAX4XaXuX. |.nXF M . X _ ,XJ o o m m m m m m m m m C m m m m m m b b b b j b 2 j j j 2 j j j j j j j j 2 j j A l.z.x.z._._.[.oXoXx.~ y % % % 8 0 0 0 5 = 5 5 = 0 8 = = 8 = 8 = = 5 = 5 8 = = 5 5 = 5 = = = 8 8 5 8 = = = 0 8 8 = , = u s.B.P.K.L.L.P.K.L.I.L.L.K.L.K.L.G.I.'.K.L.G.I.F.cXLXT.XXG.L.K.K.K.K.Y.6 u 9 u w u q t 9 9 q 5 $ $ 5 $ = % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX8 q y x x x R R R ^ @.sX6X@.pXpX6X7X2.6X XP sXMX1 2.ZXiX] fXZX^ pXZXo . o.iXB _ ' : ..X.; o k m m m m m m m m m m m m b m m b m b m j b j j j j j j j j j j j j j 2 j 2 A x.z._._._.x.h.R 0 % % $ 0 w 0 5 = # 5 8 8 8 5 0 = = = 5 = 8 8 8 = 5 = 5 = 8 8 8 8 5 = = 8 8 = = = 8 5 9 = - - 8 s 0.B.K.K.K.K.K.K.K.K.K.G.G.L.K.K.K.K.L.G.zXJXL.L.K.K.L.KXJXmXLXI.K.K.K.P.L.B.9 u w 6 u t 9 q q 9 t q $ $ 5 $ 8 & UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX5 w e x y l x R f k.^ uXqX@.6XnXsX6X#.6X6X#.fXsXh #.sX|.h fXGX@.3XfX..8X@ |.MXV _ aXb X Y ,XH X . q e j b m C 2 2 m m m m b k b b m b b j m k j j j 2 j j 2 j j 2 j j j r j 2 R z.x.h.c y 8 < # 5 0 q 8 $ $ = 5 0 8 5 5 8 = 5 5 8 5 8 5 8 = $ 8 8 = 8 = 8 8 = 8 = 8 8 5 = = 5 8 8 8 = * # s 0.B.L.L.K.K.K.K.K.K.K.K.L.G.'.{.F.L.K.K.L.K.I.LXLXI.K.L.G.`.DXmXJXxX'.F.L.K.K.K.i.0 u t t 9 t 9 6 9 w 9 $ $ $ $ 5 8 @ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX= e 0 x y x x x x ^ #.7X6X#.0XrX7XaX7X6X6X#.uXsX^ <.sX X|.fXZX4X3.fX,X3XC L MX X ,XJXh . ; X.@.2 . . q w w e m m m m m m m m m m m b m m j b j b j 2 j b j j j r 2 j j j b j 2 > y l 0 - % $ 8 0 0 8 = $ 8 8 8 0 = 5 8 5 5 0 5 5 8 8 = 8 5 = 8 = 5 8 = 8 5 = 8 8 8 $ = = = 8 0 8 # , = s 0.Y.L.K.K.K.P.P.K.K.K.K.L.L.K.I.A.xXxXA.U.G.K.L.F.'.hXkX`.F.I.G.`.kXI.GXkX{.F.I.K.I.L.i., u t t 9 q t w 9 t w 8 $ $ $ $ = @ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX& e 0 y t c d c d j.^ <.iX@.3.nX@.7XaXpX7X|.6XaX#.#.sX6Xb uXnX|.5X9XL | <XtXqXP ] MXh o G 1.` X 8 w q q q e 2 m m m m b m m b b b b b b j j j k j 2 j j 2 j j j j r > 0 0 = = = 8 0 q 8 8 $ = 5 8 8 8 8 8 5 5 5 5 8 = = 5 8 8 = = 8 = 8 = 8 = 5 = = 8 8 = = = 8 8 8 8 # * 5 s 0.Y.K.K.L.L.P.K.K.L.K.K.K.K.K.K.K.G.U.A.xXzXS.I.K.K.U.A.kXjXXX{.S.U.F.[.jXL.`.zXcXA.I.K.H.K.a.$ 7 q u 9 6 t t 6 t 6 $ 5 $ $ $ = o UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe 0 y d y y x u x.P 3.aX^ T nX7X#.sX6XpX7X7XsX[ #.7XiX@.ZXaXaX@.2.4XJ n eXMXrX 4XMX4X* - { +Xk w w e w w q q e k m m m m m m b m j b k b b 2 b b j j r r r r 8 8 $ 8 8 8 0 0 = 8 = 8 = 8 8 8 5 8 = 8 8 = = = 5 = 5 = 5 = = 5 8 5 8 8 5 = 8 5 8 = = = = 0 8 = - - 8 s i.Y.K.I.K.K.K.K.K.K.K.K.K.K.L.K.K.K.K.K.K.U.A.xXxXA.U.K.G.K.A.cXkXXX{.S.T.A.xXzXS.F.kXFXS.U.S.[.{.p.e 7 t t w t w 9 9 9 q $ $ $ 5 $ = UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXu 5 y x d y x u j.^ [ MX^ 3.pX X1.pX|.pXpX4XpX|.1.6XiXV uXZX7X1XT @.P eXnXtXN _ nX@.. . . S m q e w q w e q q e q q e r k k b j k j j j r 2 e > q 8 q 8 5 5 8 8 8 8 8 = 8 = 8 8 8 8 8 8 8 5 8 8 8 8 8 0 5 5 = = 0 5 = 0 = 8 = = 8 8 8 5 = = = 8 9 5 8 - - 8 s i.Y.K.K.P.K.K.K.K.K.K.K.K.L.K.K.K.K.K.K.K.K.K.K.L.I.LXzXA.I.K.L.kXA.xXjX[.{.S.T.A.xXzXF.L.`.zXS.U.A.zXDX9.k d q u 9 6 9 t w t q $ $ $ $ $ = UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXw w y x t d c 7 j.|.j nX^ 2.aXeX@.6X@. XsXaXpX|.@.pXpXh <.ZXO.T T tXE n fXqXqX' rXiX X' 8 w w w e w w q w w q w w q w w q q q q q q q q $ q $ q q q $ $ q $ q $ q 8 8 8 8 8 8 8 8 8 8 5 5 = 8 8 = 8 = = 5 5 0 = = 8 = 8 8 5 8 = = = 8 6 8 = - * 9 f i.Y.K.L.K.K.P.K.K.K.K.K.K.L.K.L.K.K.K.L.K.K.K.K.L.K.I.S.jXKXjXA.I.G.I.LXL.hX`.XX'.A.K.A.kX[.F.K.T.zXI.I.G.cXKXd.e d 9 t 9 9 t 9 9 9 q 5 $ $ $ # = UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe 9 t t y t x t ! ^ , qX@.@.pX@.<.fX@.O.6X6XpXpX<.<.sXI <.MX|.O.<.ZX^ rXaXqX 8XGX X: , o = e q w w w w e q e q e e e q q w q q q q q q q q q $ q - q q q $ q $ 8 8 8 8 8 8 8 8 8 8 8 8 = 8 5 8 8 8 8 = 0 5 = 5 5 = = 8 5 = = = 8 0 8 8 - - 5 s.i.Y.K.K.K.K.K.K.K.K.K.K.L.K.K.K.K.K.K.K.K.K.K.K.K.K.L.K.G.I.S.kXKXSXK.I.K.I.KXxXcXI.jX'.L.XXS.kX`.G.K.U.KXLXI.L.KXLXv e t t 9 w u 9 t w 9 5 $ $ $ $ $ # UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXw q t y t y d d v W O qX|.T 9Xb #.uXiXP 6X7X|.6XaX7X6X@.<.sX4XI 1.iX@. ZXpX|.X 4XGXqX|.+ { @. # e w w w q e e q w q q q q e q e e q q q q q q $ $ q q q $ q $ q q $ q - 0 8 8 8 8 8 8 8 = 8 8 8 8 8 8 8 = 0 5 5 5 8 5 5 5 = = 8 8 5 = - # 6 f b.Y.K.K.K.L.K.K.K.K.K.L.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.L.K.K.L.G.I.A.hXcXJXU.F.K.T.KXDXJXL.kX`.K.JXI.SXU.L.I.`.KXKXU.kXKXLXz u t u u t t 9 9 9 t q $ $ $ $ $ % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX8 q t t d d y d t ) |.o. X4 0X [ aXnXI aX6X7X<.6XiX9X|. XsX7XI @.iX X ZXpXqX_ 1.AX#.4Xk.N | / X % e q q e e q q e w e w e e q w q q q q q q q - q q q $ $ q $ q $ $ q $ q 8 8 8 8 - q 8 8 8 8 8 5 = 5 = 8 = = = 5 8 8 = 8 8 9 0 = - - 6 s.i.Y.K.K.K.K.P.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.L.K.K.K.K.K.L.L.K.L.L.K.K.L.G.cX'.SX`.XXF.XXKXKXJXF.kX`.hXSXI.LXU.G.A.'.KXKXSXKXcXSX~ s y 7 t 9 w 9 u q q 8 $ $ $ $ = % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX= q t t d d t t d e H 3XqX: 9X> T uXiXL 5X6X7X@.uX@.aX7X2.sX6X@.@.6X X. 2.MX7X< <XPX^ , , ,XMX, F % w w e q q q w w q e q w w q e w q q q q q q q $ q $ $ q 8 8 8 8 q 8 8 8 8 8 = 8 = 8 8 8 8 = 8 5 = 8 8 8 8 8 = = = 5 9 0 = * - u s.i.P.K.L.L.K.K.P.K.K.K.K.K.K.K.K.K.K.K.K.K.K.L.L.K.K.K.K.K.K.L.G.G.L.G.G.L.L.K.I.JXI.zXDXLXG.hXKXKX[.A.xX{.KXcXU.SXF.'.jXFXKXKX{.KXzXDXW 6 t t 9 t q 9 t w q 5 $ $ 8 $ = % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX$ w w t t y d d d ) K j 4X_ 1XX 1 fXnX[ <.pX|.#.uX6X@.iX9XiX5X@.2.sX6X< 2.uXiX|.|.PXU | F * LX<X| % e q q e e e e q e q q w q e q q q q q q q 5 q $ q q q $ 8 q 8 8 8 8 q 8 8 8 8 8 8 8 8 8 8 8 8 8 5 8 8 8 = = = 0 0 # * # u s.b.P.L.K.L.K.L.L.K.K.L.K.K.K.K.L.K.K.L.K.K.K.K.K.K.K.G.K.K.L.K.K.L.G.'.'.S.[.'.F.L.K.U.JXL.mXKXLXK.hXKXJXL.I.cXkXKXXXhXJXkXSXjX`.[.JXzXJXkXvXD 7 t 7 t 9 6 9 9 u 9 $ $ $ $ $ = @ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX# q w t d d d t d y ` B @.| j N uX4X1 <.aX@.@.iX6X@.7X6XaX6X X#.6X6X< 0XsX6X^ @.PX X^ & h MXh 4X_ . & e w w w q q e q e w e q q w q q q q q q q q $ q q q 5 q 8 q 8 q 8 8 - q 8 8 = 8 8 8 8 8 5 5 8 8 = = = 8 9 8 = - # 7 s.B.P.K.K.K.L.P.K.K.L.K.K.L.K.K.K.K.K.L.I.K.K.K.K.L.K.L.L.`.xXL.K.L.K.K.L.G.JXhXG.KXhXS.I.F.T.DXK.SXDXJXK.hXKXLXU.K.mXxXKXDXJXxX'.K.A.K.A.[.cXjXK.Y.9 t t t t e 9 9 9 q q 5 $ $ $ $ 8 @ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX@ w q t t d d d 7 t ' * ; OXo.O J _ . @.aX@.[ pXqX#.7X6XqXaXaX@.7XsXh 0XsX6X[ 1XZX XP C |.ZXh , 3Xm @ e w w q w w q e q w q e w q w q q q q q q $ q $ - q q $ 8 8 8 8 8 8 $ q 8 8 8 8 8 8 5 8 8 = = = 0 6 5 # - # u 0.B.P.L.L.K.K.P.K.L.L.K.K.K.K.K.K.K.L.K.I.S.XXzXF.I.K.K.K.K.L.A.hXKXI.K.K.K.L.L.I.KXSXcXKXhXF.U.S.SXmXA.JXxXDXI.jXKXcXG.{.LXDXKXcXJXK.S.L.I.I.L.T.JXjXF.a.5 u 9 9 9 9 9 9 u 9 q $ $ $ $ $ 6 o UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXw w t d d d d 7 7 o * D > ' ,X, , T MX|.P uXiXI 6XsX X<.aXpX|.6X[ 6XsX7X|.1XZX6XU n eXiX[ ^ NX3Xh . @ e q w q e q e q e w w e w e q q q q q q q q q 8 q q q $ 8 - q q 8 8 8 q - $ 8 8 8 8 = = = 8 9 9 = - = u s.B.K.L.K.K.P.K.K.L.K.K.K.L.K.L.L.K.K.K.K.K.K.U.A.zXDXS.I.K.K.L.G.`.[.FXSXU.K.K.K.L.G.'.LXKXJXmXxXA.`.T.KXkXA.xXXXJXI.kX[.jXcXSXJXSXKXzXDX`.K.L.G.K.K.L.KXkXS.a.= t q t 9 9 9 q w 6 q 5 $ $ $ $ = UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXw q t d d d d d 7 . b K o J ,X_ j 4 ZX|.V pX6X[ <.6X7X#.6XsXpX9X#.0X6X6X@.3XZX6X^ AXqX#. qXP ' CX8Xo X e e w w q e w w q q e q q e w q q q q q q q $ q $ $ q q 8 q q $ 8 8 8 8 q q 8 $ = 8 8 9 8 # - 8 s 0.B.K.K.L.P.K.P.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.L.G.I.A.kXJXG.L.K.K.L.G.xXKXmXkXJX`.K.K.L.F.zXJXKXDXcXcXA.SXhXLXxXA.L.A.SXJXJXFXkX'.L.LXLXKXcXjXG.K.K.L.K.L.L.KXjXF.a.% 7 6 9 9 9 w 9 w w q 5 $ $ 5 $ = UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXw q d d d d d d d . J M X N B <X: 3 ZX7X3 uXiXU @.sX|.I 7X7X7XfX7X2.6X7XiX8XZXiX^ + ZXqX#. aX@. 1XLX@. . & e w w q q e q q q q q q w q q q q q q q $ q 8 8 q $ 8 8 8 8 $ q 8 8 q 8 8 8 8 q 8 8 - & 8 s 0.B.L.K.K.K.K.K.K.K.K.K.L.K.K.K.K.K.K.K.L.K.K.K.K.L.K.L.G.I.A.xXLXG.L.K.K.K.U.JXKXjXXXKX[.F.I.G.`.DXkXKXzXxXxXS.KXDXDX{.S.[.kXDXKXLXU.A.G.I.KXDXLXFXXXG.L.K.K.L.K.U.LX`.K.a.q t q t 9 9 9 9 6 q q $ $ $ $ $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXw q t d v d d v s o O ... ; ; , 4X_ nXsXO uXMXU @.ZXsX1 7XuX|.7XpXiXqX5X|.3XMX6X#.1 pX|.|. uX|. 2.fXPXX.< * . & q w e w w w e q w e w w q e e w q q q q q 8 q - q q q $ 8 8 8 8 8 8 $ q 8 $ q - - = s a.B.L.K.L.L.P.K.K.L.K.K.K.L.K.K.K.K.L.K.K.K.K.K.K.K.K.K.K.K.K.L.K.`.KXJXL.K.K.L.G.`.SXkX[.XXKXjXS.I.F.`.DXXXzXA.cXkXG.SXJXcX[.SXmX[.zXKXJXU.I.I.I.JXDXLXKXhXS.I.K.K.L.G.U.JXU.K.s.8 9 6 9 9 9 t 9 q 9 5 $ $ $ $ $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q w v d v v v s @ . o F r Y < < | 3XnX< tXMXP #.ZXeX+ 7XsX@. X|.uXsX<. XaXsX6X|.3 9X3X X< uX7X 2.fXMX3XVXeX X 2Xe q q w q e q e e e q q w e q q q q q q 8 8 q 8 q 8 8 q 8 8 q q 8 8 q $ 8 8 $ d w.p.Y.K.K.K.P.K.K.K.K.L.L.K.K.K.K.K.K.K.K.K.K.K.K.K.K.L.L.K.K.K.L.G.I.A.kXmXDXT.G.K.I.F.'.kXA.I.F.mXzXA.U.S.`.zXF.`.A.xXSXjXSXSXJXhX'.G.A.hXKXmXK.L.F.`.DXxXKXKXhXS.I.G.L.K.K.L.T.L.K.f 8 9 q 9 6 q q 9 9 9 q $ $ $ $ $ % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX5 q 7 d d v v s v @ O N . j Y X.tXM X.MXh 4 uX1X+ @.aX@.#.|.7XqXpX7X@.aX5X#.I 6X6X6X< 6X X 3.aXiXX.C rX5XS -.q w q w q q e w q w q e w w q q q q q q 8 8 $ q 8 8 8 8 8 8 - 8 8 8 $ q $ t B.W.!.m.Z.K.L.K.K.K.K.L.K.K.K.K.K.K.K.K.K.L.K.K.L.K.K.K.K.K.K.K.L.K.K.K.U.A.zXcXDX`.G.K.I.A.hXzXA.`.A.xXxXA.`.S.XXkXA.G.A.xXKXJXcXkX{.A.G.I.G.hXKXhXS.I.G.U.jXjXKXKX[.F.L.K.L.K.K.K.G.K.K.s q t 9 t 9 q u 9 9 9 8 $ $ $ $ = % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX% q 7 v v v v s d.d ; | * o <Xo N eX; qX|.; uXP . 2.aX|.#.6XuX7X1.aX9XiX6X#.T 6XsX@.h 2.6X. @.tXiX^ 0XCXtX_ % t q w w w q w e q e q q q q q q q q q q q q q 8 q 8 8 q 8 8 8 8 8 8 q - s Y.Y.Y.B.Y.K.K.K.K.L.K.K.K.L.K.K.K.L.K.L.K.K.K.K.K.K.K.K.I.L.K.L.K.K.L.G.I.A.kXzXDXT.G.K.I.A.xXxXA.`.A.zXmXS.U.A.cXzXI.zXcXJXJXSXL.A.K.I.L.L.S.jXKXjXS.I.K.G.K.A.xXSXS.I.K.K.K.K.K.K.L.K.K.7 9 7 q u 9 q 9 w w 6 8 $ $ $ $ $ & UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq 7 s v v v d.d.d.d . , J O [ : <XX.eXL 3 fX,X; 2.aX#.^ 7X6X X#.7XaXpX5X|.@.<.iX@.V 2.7XX #.pXiX^ 0XfXtX| k q w q q e w w q e q e q q q q q q q $ q q q - 8 8 q 8 8 8 8 q 8 8 - q $ d !.W.!.Y.Y.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.L.K.I.A.jXzXA.I.G.L.K.K.I.F.kX[.DXT.K.K.I.S.zXhXA.U.F.hXKXhX'.XXJXJXcXzXI.hXKXJX`.I.L.K.G.I.S.zXSXF.I.K.L.G.T.S.xXzXS.I.K.L.K.K.K.K.K.L.K.u 9 9 q 9 9 9 6 q q q 8 $ $ 5 $ = @ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXk 7 v d.v d.d.d.c d.v X X o -., . 3XB ] ` 1 gXY 8XPX^ U #.sX XT 6X0XyXaX7X2.7XsX X^ 3.|.< 3.aXaXO. 2.gXMX< q w q w q w e q q e w q q q w q q q q q 8 q q $ 8 8 8 8 8 8 8 8 8 8 = q # d !.W.!.Y.P.K.L.K.K.K.L.K.K.K.K.L.K.K.K.L.K.L.K.K.K.K.L.F.DXcXS.I.K.L.K.K.K.S.kX{.JXT.K.K.L.F.kX'.G.U.A.hXKXLXKXJXSX'.K.S.S.[.JXLXL.K.K.K.K.I.S.jXjXA.I.K.K.G.I.A.cXxXA.I.K.L.K.K.K.K.K.K.Z.6 t 9 9 9 9 9 9 q 9 9 5 $ $ $ $ 8 @ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq t v d.d.d.d.d.d.d.d.v X . o Y * _ o . |.[ fX_ B J nX^ 3 2.ZX X4 7XCXqX1XqX6X XsXiX^ 3. Xh T nXiX@.. <.aXMX; 5 w q w q w w q e q q w e q q q q q q q q $ q q q $ 8 q 8 8 8 8 8 8 8 q - d Y.!.Y.Y.Z.L.K.K.K.K.K.K.K.L.K.K.K.K.K.K.K.K.K.L.K.I.F.[.DXSXL.L.K.L.G.[.LXXXcX'.DXI.G.K.K.U.JXU.A.K.'.SXKXcXhXDXjXA.I.L.U.A.xXFXF.I.K.K.K.L.L.K.L.L.K.K.K.K.I.S.SXhXS.I.K.K.K.K.L.K.K.K.B.q 9 9 9 9 q 9 9 9 9 8 $ $ $ $ # 5 o UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX@ e 5 v v v d.d.c c d.v v % . | m ; o : 1.ZXJ O < 7X X1 #.ZX XV @.ZXMX@.<.sXpX<.2.@.3.|.U T iXqX|.. #.eXMXh # t q w q q w w q w e q q w e q q q q q q 8 $ q 8 q q $ q 8 8 8 8 8 8 8 = d !.!.P.Y.P.L.K.K.K.K.K.L.K.K.K.K.K.K.K.K.L.K.K.L.G.I.A.zXcXSX`.G.K.I.F.kXKXkXGXI.SXjXS.`.F.I.SX`.jXSXcXSXLXG.F.KXkXF.I.F.I.S.cXxXA.I.G.L.K.K.K.L.K.K.L.K.K.K.I.G.cX'.F.L.K.K.K.L.K.K.P.K.B.q u 8 9 9 6 q q q 9 q 5 $ $ $ $ # UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX@ k 5 t v v v v d.d.c v v v % J 2 2 o X > fX3X] > tXiXh uXZX|.1 <.uXGX@.7X6XaXiX5X^ T #.^ T 6X5X X; @.qXiXO.% e 8 w w e q e e e q w w q q w q q q q q 8 - q 8 - q q 8 8 8 8 8 8 8 8 # d '.!.!.Y.Z.L.K.K.L.K.K.K.K.K.K.L.K.K.K.K.K.K.K.L.K.I.A.zXmXDX`.F.K.K.K.SXKXSXLXG.zXxXA.G.K.jXJXmXzX{.S.'.[.K.U.KXkXA.I.G.U.A.cXxXA.U.G.L.K.K.K.K.K.K.K.K.K.K.L.G.[.U.K.K.K.K.K.K.K.L.K.L.i.- u q 9 9 q q w 6 q 8 $ $ $ $ 5 # UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXl - q q v v v v v d.v v c d ) J ; . S ,XJ <XH P qX^ 6XZXtX* 2.1.^ T 5XqX7X XaXqX@.I ^ @.iX7X X< 3.rXpX[ # u 8 e w q w w q w q q q w w q q q q q q 8 $ q $ q $ q 8 8 8 - 8 8 8 q # d !.Y.P.P.P.L.K.L.K.K.K.K.L.K.K.K.K.K.K.L.K.K.K.L.K.I.A.xXcXDX`.K.L.G.U.JXFXKXLXA.zXzX'.SXcXJXcXG.S.S.I.K.G.K.I.LX[.F.I.G.I.A.cXxXA.I.G.L.L.K.K.K.K.K.K.K.K.K.K.L.F.K.L.K.K.K.K.K.K.K.P.G.p.5 7 8 9 q 6 w 6 w 8 8 $ $ $ $ $ # UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXl q w q q l v v v v d.d.v v h.y , O X 1.. b 3X3XiX^ 8XZXZX3 6X0X .T #.6XiX@.2.aXuXT |.T 6XuX X1 3.pXiXU 0 0 0 0 q w w q q e q e w q q q q q 8 8 8 8 q q q $ q q q 8 8 q 8 8 8 8 = d !.U.U.!.H.P.K.K.K.K.K.K.K.K.K.K.L.K.K.K.K.K.K.L.G.I.A.zXxXFX`.S.L.K.`.JXT.SXjXK.DXLXFXKXXXkXxXA.`.L.K.K.L.G.U.JXI.G.L.K.L.G.{.`.G.L.K.K.K.K.K.K.K.L.K.L.K.K.K.L.L.K.K.K.K.K.K.K.K.K.G.D.d.7 u 7 6 5 9 9 w 6 9 9 $ $ $ $ = & UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q q - q t v v c v c c v v c t . , * . k. F C X.MX| K ZXeXO 5X2.K J T 6XsX^ <. X9X1X[ @. X<. X^ 3.iX6X^ k 6 0 9 w q q e w q q w q w q q q q 8 8 8 8 q $ q $ q $ 8 8 8 8 8 8 = 8 % d `.!.!.G.!.G.K.K.L.K.K.K.K.K.L.K.K.K.K.K.K.K.K.L.G.I.S.kXjXDX'.kXJXF.K.JX`.XXmXcXJXxXhXKXXXjXXXS.L.G.L.K.K.K.L.{.I.K.L.K.K.L.G.G.L.K.K.K.K.K.K.K.K.K.K.K.K.K.L.K.K.K.K.K.K.L.K.K.P.V.a.w.v s d d d 7 7 6 q q q 8 $ $ $ $ 8 % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q q q q q l x v v v v x x v v t . , , . O ] > nXnXT KXrXK 1.9XC Y #.iXsXL <.7X2.5X7X2. X7XqX^ T aX9X XA 5 e 9 e q w q q e q q q w q q q q q q q 8 8 q $ q $ q 8 q 8 8 8 8 = 8 # d '.!.`.D.cXXXS.I.K.K.K.K.L.K.K.K.K.L.K.K.K.K.K.L.K.I.A.kX[.DXDXDXLX[.{.KXSXxX[.A.zXcXSXKXkXzX`.F.I.K.K.L.L.K.L.G.K.K.K.K.K.K.L.L.K.L.K.K.K.K.L.K.K.K.K.K.L.K.K.K.K.L.K.K.K.K.Z.Y.a.a.v w.v v d d d d d t 7 w 9 q $ $ $ $ 8 @ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX- e q q $ q $ t z v v v v c c c v d . O ; , ..o XiX+XAX7X 2.0XH ' 3.pXaXh <.6XuX<.iXuXqX7X6X^ 4 iX6X XR % e q q q q w q q e q w q e q q q q 8 q 8 8 q 8 q 8 8 8 8 8 8 8 8 - q $ 7 '.!.!.!.HXzXA.U.K.L.K.K.K.K.K.L.K.K.K.K.K.K.L.K.G.I.G.zX`.`.KXzXSXKXmXSXU.S.G.A.xXcXDXKXcXDX`.G.L.K.K.K.K.L.L.L.L.K.K.K.K.K.K.K.K.K.K.K.K.L.K.L.K.K.K.L.K.K.K.K.K.K.K.P.Z.B.a.d.s.d.s.v v s d v d d d d d d t 5 $ $ = $ 8 @ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXy q q q q q q d v x x v v c v x x 7 - ; _ o 4XeX> PX<Xn 2.aX; K T 7XZXh O.6X5X2. X2.tX6X7X|.3. X6X@.A 5 w w q w q w w q w q q w q q q q q q 8 q 8 8 8 8 8 q 8 q 8 q q 8 8 q % d ].].].*XcXcXSXT.G.L.K.K.K.K.K.K.K.K.K.K.K.K.K.L.I.K.F.cX'.XXKXDXkXDX'.hXF.I.`.A.xXcXSXKXcXDXT.K.L.K.K.L.K.K.K.K.K.K.K.K.K.L.K.K.K.K.K.L.K.K.K.K.K.K.K.K.K.K.L.K.K.K.Y.B.f.w.a.a.d.s.v v v s v d d d d t t q 5 $ 5 $ = # 8 @ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe $ q $ q q $ y v x x c v c v v x x e 3XX [ iX ..8XG @.0X3 ,X3 6XMXh 4 6X<.2. X2. X7XrX X3.6X9X7X^ % e 9 e w q q q q q w q q q q q q q q q 8 8 8 8 8 8 8 8 q - 8 8 8 8 8 $ t ].].].>XhXJXKX[.G.L.K.K.K.K.K.K.L.K.K.K.L.I.I.G.S.L.[.LXDXxXKX{.F.DX[.kXF.K.I.S.zXDXhXLXFXkXK.K.K.K.K.K.K.L.K.L.K.K.K.L.K.L.K.K.K.K.K.K.K.L.K.K.K.K.K.K.K.K.L.P.B.a.a.a.a.g.d.w.w.d.d.s.v d v d d v 9 6 $ $ $ $ $ $ 8 % 8 UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq $ q q q q q t v x x c x v c v x v % j + < 4XX * ; o.9XuX* j T 6XZX< T 6X7X9X7X6X6X#.9XiX2. X6X Xj.% e 8 q q e 0 w e q q w q w w q q - q 8 q 8 8 q 8 8 8 8 8 8 8 8 8 = 8 $ s ].*X].hX*XKXKXLX'.G.L.K.L.K.K.K.K.L.I.I.G.S.F.'.zXDXJXLXU.A.[.I.`.DXhXkXA.I.L.F.jXKXjX[.KXjXS.I.K.K.K.K.K.K.K.K.L.K.K.K.K.K.K.K.L.K.K.K.K.K.L.K.L.K.L.K.G.V.B.a.f.a.g.g.a.w.s.v w.v s v d v d d 9 $ $ 5 $ 5 $ $ $ $ = $ 8 UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe q q q q q 8 t v c x x v v z v x v % * ,X. , @.gX3 .@.<.ZX^ V uXsX0XqX6X X#.2.qXfX7X7X@.o.# u 0 w q q e q 0 e q q w q q q 8 q q q q q q q 8 8 q 8 = 8 8 8 8 8 8 $ 7 ].].].*XjXLXLXKXkXA.I.K.L.K.K.L.L.K.F.S.T.jXSXSXzXJXcXKXI.I.G.F.`.SXcXzXA.I.L.S.jXKXI.F.KXkXA.I.G.L.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.L.K.L.K.K.K.K.K.D.n.f.f.a.g.g.a.g.p.d.w.d.v w.w.v f d d 9 $ $ $ 5 $ $ $ $ $ $ $ $ $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX% q q $ q $ q q e x v v x x z c v x v 9 ' * & P AX,X` K <.LXiX1 7XpX6X6X7X XU 2.iX0XpXaX@.) 5 9 9 q w q q w q e q w q w q q q q $ - q q $ 8 8 8 8 q q 8 8 8 8 8 8 $ 7 !.].].*X].SXxXSXxXA.I.G.L.I.L.F.G.I.[.cXDXzX{.F.G.JXJXSXG.K.L.K.U.JXJXzXA.I.L.G.{.JXI.U.KXXXS.I.K.K.K.K.K.K.K.K.K.L.L.K.K.K.K.L.K.K.K.K.P.P.K.K.Y.B.f.f.f.R.f.p.g.g.w.d.d.d.w.w.v f v t 9 $ $ 5 5 $ $ $ $ 8 $ $ $ $ $ $ % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX& q $ q q q q $ e v x x v c v x v x v 9 J . , T fX; 1X1.<.AX> 3 9XpXpX6X6XiX^ #.qX5X|.pXuX|.% e 8 e q w q q q q q q q q q q $ q q 8 q - q 8 8 - 8 = 8 8 8 8 8 8 8 5 t ].].*X].!.[.L.jXSXF.U.L.G.A.K.'.SXJXcX[.I.A.F.L.`.LXLXU.G.L.K.K.I.LXKXjXS.I.K.G.U.[.F.U.JXT.K.L.K.K.L.K.K.L.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.P.B.B.f.f.f.f.E.f.g.g.g.g.g.a.w.d.d.w.f u 6 $ $ $ $ 5 $ 5 5 $ = $ $ = $ $ $ $ # UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX@ q q q - q q q w x x x z z z x v x c t ; AXo G 3XuX8X, <.6XfXpXsXiX X^ 3.qX2.@.@.0XqX$ e q w q q q e w q w q q q q q q 8 q q = q 8 q 8 q = q 8 8 8 8 8 8 8 q t ].].].].!.F.G.{.KXjXA.G.`.hXSXcXKXzXA.G.K.I.I.G.T.KXLXI.K.K.K.K.I.JXKX[.G.L.K.K.K.G.K.U.JXU.K.K.K.K.K.K.K.K.K.L.K.K.K.K.K.L.K.K.L.K.L.Y.B.B.f.E.R.R.E.E.g.g.g.g.a.d.d.d.g.d.v t $ $ 5 5 5 $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe q q q $ q 8 w x z v z c x z z v l t o . O -.5X. : V 9XVXo , 2.6XiXsXaX Xh T iXuX@.I <.|.0 0 9 w q q q q q q q q 0 q q q 8 q q 8 q q $ q $ q $ 8 8 8 = 8 8 8 8 = u ].].].].!.I.U.A.SXzX[.FXSXzX'.F.LXkXF.U.K.K.K.L.xXKXLXI.K.L.K.L.G.DXzXG.L.K.L.K.K.L.K.L.I.K.L.K.K.K.K.K.K.K.K.K.L.K.K.K.P.K.K.P.K.Z.B.f.f.R.E.R.R.E.g./.g.g.w.g.a.d.d.w.v 6 $ $ $ $ 5 $ $ 5 $ $ $ 5 8 $ $ $ $ $ $ $ $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q q q $ q 8 w x z x v z v c z v x t o . O -.o M ' 3.fXH o.5X5XiX5XeXiXh 4 iX6X X[ 2.|.0 8 w q q r q q w e q q w q q q q q 8 q 8 q $ $ q - q 8 = 8 8 0 8 = q = t ].].].*X!.A.L.'.DXLXzX[.I.S.G.I.KXxXS.L.G.L.K.I.LXKXLXI.K.K.K.L.G.'.I.G.L.K.K.K.K.K.K.L.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.P.Y.a.f.f.R.R.f.R.f.E.f./.g.g.g.a.d.d.d.v 7 # # 5 8 $ $ $ $ $ $ $ 5 $ $ $ $ $ $ $ $ $ $ $ % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXw - $ q 8 q 8 q z c z x z z z z x x t @ ` H 1 & 0X8X. 3X0XfXiX2.7X7XU 4 sX7X|.I 1. Xy 5 e q q q q w q e q w w q q q 8 8 q $ $ q q q 8 q 8 = 0 8 8 8 8 8 8 $ t ].*X*X].].hXDXcXzXcXA.[.JXI.I.I.KXSXL.I.L.U.S.[.hXkXJXG.I.K.K.K.K.G.K.L.L.K.K.K.K.L.K.K.L.K.K.K.K.L.L.K.K.K.K.L.K.K.L.K.V.B.f.a.R.R.R.a.E.R.g.g.g.E.E.g.a.g.g.d.t 5 % $ 8 $ $ $ $ $ $ $ 5 8 = = $ = $ $ $ $ $ $ $ $ 8 UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q 8 q 8 q q q x z x z c c c z x v x @ * _ . ; k.<.0X , aXZXiX@.8XiXh 1 uXpX@.L @.qXA # w q q e w q q q q 0 q w q q 8 q = q q q $ 8 8 8 8 8 8 8 8 = = 8 8 $ 7 ].*X*X].jXzX'.S.K.DXhXKXLXI.G.U.JXLXK.F.F.L.A.cXhXjXSXA.I.G.L.K.K.L.K.K.K.K.K.L.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.L.Z.Y.R.f.f.R.E.R.R.E.W.f.l.g././.g.g.E.d.v t - % $ 8 5 $ $ $ $ 5 5 5 $ $ $ $ = $ $ $ $ $ $ $ $ $ $ 8 UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q 8 8 8 q $ q x z v c z x z z x x d % b o. + K b S o 5XAXGX3.aXMX1 + 0X8X XV 3. Xh 5 t q q w q q w q w q q q q q 8 8 q 8 8 q = 8 q 8 8 0 = = = 5 8 = 8 # t ].*X].].!.A.G.K.T.JXJXKXLX`.S.'.zXDXFXU.[.XXA.DXI.hXxXA.I.K.L.K.K.K.K.K.L.K.K.K.K.L.L.K.K.K.K.K.K.K.K.K.K.K.L.Y.B.f.R.R.f.B.R.E.R.f.f./././.E.g.g.g.d.f 9 $ % 5 8 $ = 5 = $ $ $ = $ $ $ = $ $ $ $ $ $ $ # = $ $ $ = UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q q 8 q q q q l z x z z c z z z x c 6 . ; @.o . ' 4XX K ZXfX3XZXMX; O uX3X Xh #. XR 8 0 q q q q w q 0 q 0 w q q q q q q q q = q 8 8 $ 8 8 8 0 = 8 8 8 8 $ 9 ].].*X].].L.I.K.I.LXKXJXSXKXS.jXkXL.KXhXJXxXF.LXI.L.L.K.L.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.K.L.K.L.K.K.K.P.Y.B.f.f.W.E.R.f.R.R.R.E.R.g.g.g.g.g.g.d.d 6 $ $ 5 8 $ $ 5 $ $ $ 5 5 $ $ $ $ $ $ $ $ $ $ $ $ $ = $ $ $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq - q $ q q 8 q x Q z z z c z z c x x 7 . ` . o F M . J KX3 <XAXMXh 9XpX X; 4 |.h 5 e 8 w q q w 0 q q q q q q q q q 8 8 8 q = - q q $ 8 8 8 8 = 5 8 8 8 5 ).].].].!.G.K.L.G.cXKXJXT.DXK.SXxXA.[.cXSXzXL.JXI.K.L.K.K.K.K.K.L.K.K.K.K.K.K.K.K.K.K.K.K.P.K.K.K.L.J.B.B.f.f.B.R.R.E.E.f.f.E.f.E.E.g./.g.g.d.7 $ % $ 5 $ $ = $ $ $ 5 $ $ $ = $ $ $ $ $ $ $ = $ $ $ $ $ $ = % $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q $ q 8 8 q $ l c x Q x z z z z x x 9 X eX. . > T & 4XGX1 9XZX X< P 7XA 8 w 0 q q w q q q q q q q q q 8 q 8 8 8 = q q $ q q 8 8 = 8 5 8 = 8 8 6 B.].].!.L.L.K.I.A.hXzXXXI.jXSXSXU.K.T.LXxXzX`.JXI.K.L.K.K.K.L.K.K.K.L.K.K.K.K.L.K.K.K.K.L.K.K.K.Y.B.f.f.f.R.R.E.W.R.R.E.E.E.E.g.l./.g.g.d.w % - = 8 $ $ $ $ $ 5 8 $ $ $ $ 5 $ $ $ $ $ $ $ $ $ $ $ $ = $ # # = UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq 8 8 q 8 q q q l z z z z Q z Q c z z q ; . . j 1.eX 2.AX X< I 7Xe e q q q q q q q q 0 q q q q q 8 q 8 q 8 q $ q q 8 $ 8 8 8 8 8 8 8 5 8 5 R.!.!.!.L.K.K.K.K.L.K.F.S.hXKX[.S.L.K.KXxXSX'.xXK.L.K.K.K.K.K.L.K.K.K.K.L.K.K.K.P.L.K.K.K.P.Y.a.f.f.R.E.f.W.).R.R.E.E.R./.g.l./.f.d.f # # $ 8 5 $ $ $ $ $ $ $ 5 $ $ 5 = $ $ $ $ $ $ $ $ $ $ = # $ $ = % $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q = q q $ q $ y z z z z Q c z z x z d o j . <XeXfXMXiXX 3 <.% e 9 e 0 q q 0 w q e q q q q q q q 8 $ q $ q 8 $ - q 8 8 = 8 8 8 8 = 8 0 R.!.W.!.G.K.K.K.K.L.K.U.S.kXKXL.I.K.I.LXXXJXLXhXF.L.K.L.K.K.K.K.K.K.K.L.K.K.K.L.K.K.L.Y.B.f.f.R.R.B.R.E.).E.R.f.f.E.R.g.g./.E.d.d $ # 5 8 $ $ $ $ 5 $ $ q $ $ $ 5 $ $ $ $ $ $ $ $ = $ $ = # $ $ $ $ # = % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq $ q $ q $ q q t Q Q Q z Q Q z z z Q x % N <XeXpXMXo 1 1.@ e 5 w 0 w q q q q q q q q q q $ q 8 q q $ 8 8 8 q q 8 8 8 8 8 8 5 8 8 9 f.W.W.P.G.K.K.K.K.L.K.K.G.`.jXL.K.K.L.XX`.JXKXkXS.I.K.K.K.K.K.K.K.K.K.K.K.K.K.L.L.Y.B.f.R.R.R.E.R.R.R.R.E.E.R.E./.g.f./.g.d.9 % % 8 8 $ $ $ $ 5 $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ % # # $ @ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q $ q q q q q t z c Q Q z z z c z Q z k . 4XGX 1 5X% e q q q q q q q q e q q q q q q 8 8 q $ q 8 8 8 8 8 = 8 8 8 = 8 8 5 8 8 a.W.m.Y.P.L.K.K.K.K.K.K.L.K.S.K.L.K.L.S.U.JXKXhXS.I.G.L.K.L.L.K.K.K.K.L.K.K.P.B.R.f.f.R.W.W.R.R.R.E./.).E.E.R.g.f.E.g.v 8 - $ 0 8 = $ $ $ = $ $ 8 $ $ 8 $ $ 5 = $ $ $ $ 8 $ $ $ $ $ $ $ $ $ % = = = UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq $ $ q q $ q q t z z z Q Q c z Q c x x % ,XtX..<.= q q q w q e 0 8 q q q q q q q q - q 8 q 8 q 8 8 8 8 8 8 8 8 = 8 5 8 = 8 i.b.B.m.Z.L.K.K.K.K.K.K.L.L.L.L.K.K.K.I.L.cXKXhXS.I.K.K.K.K.K.K.K.K.L.G.Y.B.f.R.R.R.E.R.R.R./.R.E.E.R.f.f.E.f.E.w.d = - = = = $ $ = = 5 = 8 5 $ 5 $ 8 $ $ $ $ $ $ $ $ $ $ $ $ # = = = $ # = = # UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q q $ q 8 q - e c Q Q Q Q Q Q Q c z z 9 ; 3XtX5Xr q q 0 q q q 0 w q q q q q q q q 8 8 q 8 8 8 8 q q 8 8 8 8 = 8 8 8 = 8 5 a.B.B.Y.Z.L.K.L.K.K.K.K.K.K.K.K.K.K.K.K.K.L.KXhXS.I.K.K.K.K.K.K.K.P.B.R.R./.R.R.R.R./.R.R.R.f.E./.f.g./.R.l.d.t = % = 8 $ $ = 8 $ $ $ $ $ $ $ $ 5 $ $ $ $ $ $ $ $ $ $ $ $ $ $ = $ # % # = % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q $ q 8 8 q q w Q z z Q c z Q Q z z z e ; ZXY % w 8 q q q q q 9 q q q q q q q q $ 8 q 8 8 8 q $ q = 8 8 8 5 8 8 8 5 q w.B.B.b.Z.K.K.K.L.K.K.K.K.L.K.K.K.K.K.K.I.F.DXzXA.I.K.L.K.K.K.Y.B.E.E./.R.W.W./.E.R.E.R.R./.f./.f.)./.g.f 0 - % 5 8 $ $ $ $ $ 5 $ $ $ 5 8 $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ # # $ = % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX$ q 8 q 8 8 q q w z Q Q Q Q z Q Q Q x z t . . CX5X@ t 5 w w q q q q q q q q $ $ q q q q $ 8 8 q - q $ 8 8 8 8 8 = $ 8 5 q s.a.b.v.Z.K.L.K.K.K.K.K.K.K.K.K.K.K.K.K.I.G.[.`.G.L.K.P.P.Y.W.R././.W./.E.).R.E.E.R.E.E.f.E.E.E.g.d.d - % = 8 = = $ $ $ $ = $ $ 5 5 $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ # $ = UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q = q q 8 q $ e z Q Q Q Q z Q Q z z x k o . . . 1.rX@ k 5 q q q q q q q q q q 5 q 5 q $ q $ q $ q q q 8 8 8 - q $ 5 $ 8 $ q s.a.i.c.Z.K.K.K.K.K.K.L.K.K.K.K.K.K.L.K.K.L.F.G.L.K.P.B.R.R.R.W.W.E.E.E./.E.E./.W.).f.f.E.E.g.d.u $ % = 8 $ $ = = 8 $ $ $ 5 = $ $ $ 5 $ $ 5 $ $ $ $ $ $ $ $ $ $ $ = # # # $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX= q 8 8 8 8 q q q z Q z Q Q Q z Q z z z l @ k @X@ e 5 q q q q q q q q q q q q q q q $ q $ q q $ - q q $ 8 8 $ q 8 $ $ q s.p.i.c.Z.L.K.K.K.L.K.K.K.K.K.L.K.K.K.K.L.K.K.P.Y.R././.E.W.R./././.)./.R.E.E./.f.E././.l.f 9 # $ 8 8 = = 5 = $ $ $ $ 5 $ $ $ 5 $ 5 $ $ $ $ $ $ $ $ $ $ $ $ $ = $ % # = $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX= q q 8 q $ $ q q x Q Q Q Q z Q Q z z l x & 8 q q q q q q q q q q q q q q q q $ q q q 8 8 8 8 8 8 8 8 8 8 q $ $ 8 $ $ w.p.a.p.V.G.K.L.K.K.K.K.K.K.K.K.L.K.L.I.K.P.W.W.E.R.(.W.E.E.R./.).)./.E.E./.f.E./.g.d.f 8 % $ 8 8 = = = $ = $ 8 = $ $ $ 5 5 5 $ $ $ $ $ $ $ 8 $ $ $ $ $ $ $ $ % # $ = UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX$ q q 8 q q 8 8 q z Q Q z Q z Q z z z l x % @ q $ q q q q q q q q q q q q q $ q $ - q 8 8 8 8 8 8 8 8 8 8 8 $ q $ q - s.s.p.i.V.!.K.K.K.K.L.K.K.K.K.K.K.K.L.Y.W././.W.E.W.(.W.).E.).).W././.g././././.d.d # # 8 8 5 $ $ 5 = 8 $ $ = 8 $ 5 5 $ $ $ $ = $ $ $ = $ = $ $ $ $ $ $ $ $ $ $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX8 q $ $ q q q $ 8 z Q z Q Q Q z Q Q x x z % . @ q q q q q q q q q q q q q q q $ q q $ q 8 q - q 8 8 8 8 5 8 $ = = 8 $ 8 s s.e.a.Y.K.K.K.K.K.K.K.L.K.K.K.L.W.).).).].).R.)./.W./././.E.E.).f././.)./.v w % # = 8 = = = 5 = = $ $ $ 5 = $ $ $ = $ $ $ $ $ $ $ $ $ $ # $ $ $ $ % % $ $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX$ q q q $ q q q q x Q z Q Q Q z Q z x l x q . . . @ q q q q q q q q q q q q q q $ q q $ q 8 8 8 q 8 8 8 5 8 = 5 8 8 = = 8 $ s s.s.e.n.P.K.K.K.L.K.L.P.P.P.W.E.E.).].).).].W.W.).)./././.R.E.E.f.).d.d 8 - $ 8 8 = = = = = = = 5 8 = $ $ 8 $ 5 $ $ $ $ $ $ $ $ = $ $ = = $ $ $ # $ $ % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX$ q $ 8 8 $ q q q l Q Q Q Q Q Q Q z Q l l t . . o q q q q q q q q q 5 q q q q q q q $ 8 q $ q q $ 8 q 8 8 8 8 8 = $ q 5 5 s s.s.s.M.K.L.K.K.K.K.I.Y.W.E.E.).].).].]./.).)././.).)././.E.).g.d.d $ - = 8 8 $ = = 8 = $ # 5 = $ $ = 5 = 5 $ $ $ $ $ $ $ $ $ $ $ $ $ $ = # # $ = % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX- q 8 q 8 8 - q q l Q Q Q Q Q z z z z x x y . . X q q q q q q q q q q q 5 q q q $ q q 8 8 q 8 q $ $ q 8 8 8 $ 8 8 = $ 5 $ s s s s.b.P.K.K.K.P.B.R.E./.W.).).].).).)./.).).E./././.E./.g.v t $ $ $ 8 = 5 $ = 8 = = $ 5 = 5 = 5 $ 5 $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ % % # - UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX- q q 8 8 q - q $ y Q Q Q Q z Q z Q Q l z y @ o 9 q q q q q q q q q q q q q 5 q - q q 5 q - $ $ q $ 8 8 = = 8 5 = = 8 8 7 s s s c.G.Z.Q.W.R./.W./.).)./.].E.).).).).)./.E.).)./.d.f 0 # = 8 5 = = = = = 8 $ $ = = = = = = $ 5 $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ # $ = UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX$ q 8 8 8 q q q $ y z Q z Q Q Q Q z D l x y @ . q q q q q q q q q q q q 5 q q q q 5 5 q 5 q q q $ q 8 8 8 8 = 5 8 8 = 8 s s a s s.Y.R.f.E.W.).).E.).).).).).)./.).).)././.l.d.d $ - = 5 = = = 5 5 $ 8 = = = $ 5 $ 5 5 $ 5 $ $ $ $ $ $ = $ $ $ $ $ = # $ % # $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX% q 8 q 8 q 8 q $ e Q Q Q z Q Q Q z z z l x % . 9 q q q q q q q q q q q q q 5 q 5 q 5 q q 5 q - q q 8 8 8 8 = 8 = 5 8 8 6 s s 7 v E.R.E.W././.W.).).W.).).).)./.).).).l.}.u $ - 5 8 = = = = = = = = $ = 8 $ 5 $ = $ $ 5 $ $ $ = $ $ $ $ $ $ $ = $ # # $ 8 = UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX% q 8 8 8 $ q q q t Q Q Q Q Q Q Q Q Q z x x % . q q q q q q q q q q q q - q q q q - q 5 q 5 $ q $ $ 8 8 = = 5 = 8 = = 5 6 s s s f./.E./.).).).)./.).).).).)././.)./.f 8 & # q 8 $ $ 5 = 8 8 # = 5 $ 5 $ $ $ 5 $ $ 5 $ $ $ $ $ $ $ $ $ $ $ = $ # % = # % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX% q 8 q q q $ q $ e Q Q z Q Q Q Q Q z z z l 5 = q q q q q q q q q q q q q q 5 5 q - q $ q $ $ $ q 8 8 8 5 = 8 = 8 8 # 7 g 7 w././.E./.).)./.).)./.)./.).).g.d.d $ # = = 0 = = = = = 5 5 = = = $ = $ $ $ 5 $ $ $ $ $ $ $ $ $ $ $ $ $ $ # # % $ 8 % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX% q $ q q q q q q e Q Q Q ! Q Q Q z Q z x x q q q q q q q q q q q q q 5 q q q q 5 q q q q q $ q 8 8 8 = 8 = 5 = 8 8 6 s g d.).g./.)./.)./.)./.g.).).g.d.t - % $ 9 = = = = = = = = # 8 = 5 = $ 5 $ 5 $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ # $ = # UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX$ q $ - q q $ q - e Q Q Q Q Q Q Q Q z z x w q q 5 q q q q q q q q q q 5 q - 5 5 - q $ $ q - $ 8 8 8 8 = 5 = 8 8 8 6 7 s /.)./.).E./.)././.).).l.d q % $ q q $ # = = = = = = = 5 5 = = 8 $ 5 $ 5 $ = $ $ $ $ $ $ $ = = = # $ # # $ $ % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX& q q 5 $ q q q q q z ! z Q ! ~ z Q z z e 8 q q q q q 5 q q q q q q q 5 q q q q 5 q q $ q q = 8 5 = 8 = 8 = 8 = 7 g w.g././././././.)./.d.d $ - $ q $ $ $ $ = = 5 = = = = 5 = # = = $ $ $ $ $ $ $ $ $ $ = $ $ $ # $ $ # # = $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX& 9 8 8 8 8 8 q 8 q z Q ! Q ! Q ! Q Q e q q q q q q q q q q q q q q 8 8 q q 8 8 8 8 8 = 0 8 = 5 8 8 = 8 8 5 5 7 s d././.g.g.).)./.v t $ $ = 5 8 # 8 5 = = = = = = = 5 = $ 5 $ $ 5 $ $ $ 5 $ $ $ $ $ $ $ $ # = $ # # $ = UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX& q 8 8 8 q 8 q $ q z Q Q Q ! Q Q Q e $ q - q q q q q q q q $ q q 8 8 8 8 8 8 8 8 = 8 8 8 8 8 = = 8 $ = 5 5 7 v /./.l./.).d.v w $ - $ 8 5 = 8 = = = 5 = = = = = $ = = = 5 $ $ $ $ 5 = $ $ $ $ $ = $ $ = = # # $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX@ q 8 q $ q 8 $ q 8 x ! Q Q Q Q Q y q q q q q q q q q q q q 8 q 8 q q 8 8 0 8 8 8 8 = 8 8 5 5 5 = = 8 $ q 6 d.)./.l.}.x $ % 8 q 8 $ $ 5 = = = = = = = 5 5 = = 5 = 5 $ $ $ 5 $ $ $ $ $ $ $ = # $ = # % # = # UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXw = q 8 q q 8 q 8 l ! Q Q Q Q y - q q q q q q q q q q q q $ q - q 8 8 8 8 8 8 8 8 8 8 8 5 = 8 8 $ 8 % t /.l.c t # - $ q $ $ 8 $ 8 = 5 = = = 5 = = = # = = $ $ $ $ $ $ $ = $ $ $ $ = # = $ $ % $ $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q q 8 q 8 8 8 5 x Q Q Q Q l q q q q 5 q q q q q q 8 q q - q $ q 8 8 8 0 = = 8 = 8 5 5 $ 8 8 = q % v v 5 # # 8 8 = = = 8 = = = 8 = = = = 5 = = = = 5 5 = = $ 5 $ = $ $ $ # = $ $ $ # # $ 8 # UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq 8 8 $ q q 8 q q l Q Q Q y $ q q q q q 5 q q 8 q 8 q q 8 q q 8 8 8 8 8 8 8 8 8 5 8 = 8 = = 8 5 8 $ $ = 9 0 5 = 8 8 $ $ $ = = = = $ 8 = $ $ 5 5 # # 5 5 # $ = $ $ $ $ $ $ = $ $ # $ = % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q q - q q 5 q 5 y Q Q l 5 q q q q q q q 5 q 8 q q 8 q 8 8 q 8 8 q 8 8 8 8 8 = = 8 = 8 = 8 8 $ 8 q = = = = = 8 = 8 $ = = = $ = = = $ 5 $ $ 5 # 5 = = = = = $ $ # = $ = # # $ $ % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq = q q = q 5 q q l Q y 5 q - q $ q q q q q q q 8 q 8 q q 8 8 8 8 = 8 8 8 8 5 = 5 8 $ 8 8 = 8 = $ = = = = = = 8 = 5 = = = 5 = $ 5 $ $ $ $ = = 5 5 # 8 # = $ $ $ $ # # $ # UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq 8 8 8 q q $ q - t x $ q q q q q q q q $ q q q $ q $ q 8 8 8 8 = 8 8 8 8 5 8 = = = 8 = = 5 = = 8 = 5 = $ = = = = $ 5 = $ = 5 $ $ $ $ $ $ $ $ $ $ $ $ $ # = # # $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq 8 8 - q 5 q q 8 q q q q $ q q q $ q q q q q q $ q $ 8 = 8 0 8 8 = 8 8 8 8 = 5 8 = = 8 = 5 = = = = = 5 = 5 = = = = $ 5 $ = $ $ $ $ = $ = $ = $ = # $ # # $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq 8 5 q 5 q 8 8 q $ q q q q q $ q q q q q $ $ q q $ 8 0 8 8 8 8 = 8 = 8 = 8 = = 8 = = 5 = 5 8 = = = = = = $ $ 5 $ $ $ 5 $ $ $ $ $ $ $ $ $ # $ # # # $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX8 q q - q q - q q q $ q q q q 5 5 q $ q q q q $ q 8 8 0 8 8 8 8 8 8 = 5 = 5 = 8 = 8 = 5 8 = = = = $ = 5 $ $ 5 = $ $ $ $ $ $ $ $ $ $ $ = = % $ = = UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX5 q q q - q $ q $ q q - q q q q q 8 q 8 q q $ q 8 8 8 8 = 5 0 = 5 = = = 8 = = = 5 = = = $ = 8 $ 5 $ 5 $ $ 5 $ $ $ $ $ $ $ $ = $ $ # % = $ % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX5 q q q 8 q q q $ q $ q q 5 q q q 8 8 $ q q $ 8 8 8 8 = 5 5 = 5 5 = 0 = 8 = 0 = 5 = = 8 = = = 5 $ $ $ = $ $ $ $ $ $ $ # $ = # % = = % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q 8 q 8 $ $ q q q $ q q q $ q 8 q $ q $ q 8 8 0 8 0 5 8 = 8 = = = 5 = = 5 5 = 8 $ $ = 5 $ $ 5 $ $ $ $ $ $ $ $ $ = = $ % $ $ % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXq q $ 8 q q $ $ q q $ $ q - q 8 8 q 8 q $ 8 8 5 8 8 5 = 8 5 = 8 5 = 5 = = = = = $ 5 $ $ $ $ = $ $ $ $ $ $ $ = # # # # = UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX8 8 8 8 = q = q 8 q 8 q q $ q $ q 8 8 8 q $ 8 8 8 8 $ 8 8 = 8 = 8 = 5 = = 5 5 = $ $ $ $ $ $ $ = $ $ $ $ $ # # $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX8 8 8 q = q 8 8 8 q 8 q q $ q 8 8 8 8 $ q 8 q 8 = = = = 8 = 5 = = = 8 = # # = $ $ = $ $ $ $ = # $ $ # # $ $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX$ 0 $ q 8 q 8 q 8 q $ q q q 8 q 8 8 q - $ $ = 8 = 8 5 = 5 = 8 = = 5 = 8 8 5 $ $ $ = $ $ = # $ % % = $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX$ q = 8 8 8 8 8 8 q $ q $ 8 8 8 $ $ q q $ 8 $ 8 = = 8 = 5 = = 5 # = 5 # = $ $ $ # $ $ = # # = $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX$ 8 8 8 8 8 8 8 8 8 q 8 8 8 8 8 = 8 8 $ = 8 8 = = 5 = $ = $ $ $ = # $ $ $ $ = $ % % # = % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX$ 8 8 8 8 8 8 q 8 8 8 8 8 $ 8 8 = 8 8 8 $ = 8 8 = = $ $ $ $ $ $ = $ = = # $ # = = % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX- q 5 8 8 8 $ 8 8 8 8 8 - 8 $ 8 8 = 5 5 = = 8 = = 5 $ $ $ $ $ $ # = # # # = % UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX% 0 $ 8 8 8 8 8 8 8 8 8 8 8 = = = = $ $ 5 $ $ $ $ $ $ $ $ $ $ # # = # UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX& 8 $ 5 = 8 5 5 = 8 5 5 = = 5 $ $ 5 $ $ 5 = $ $ $ # = $ = % = # UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX@ 8 $ 8 = = 5 5 = $ 5 $ $ $ 5 $ $ $ $ $ $ $ $ = # # # = $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX0 = = 5 5 8 = 5 5 8 5 8 5 $ 5 8 = 5 5 = = = = 5 $ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX& # % % % % % % % % % # % % % % % & % % % @ UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXe e e e e u w 9 0 9 e 9 w e e e e UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXo o o o @ o @ o o . UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX",
"UXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUXUX"
};

View File

@ -0,0 +1,73 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#if defined(__linux__) || defined(__FreeBSD__)
#include <sys/param.h>
#endif
#define CUBICSDR_TITLE "" CUBICSDR_BUILD_TITLE
#ifndef __BYTE_ORDER
#ifdef _WIN32
#define ATTRIBUTE
#define __LITTLE_ENDIAN 1234
#define __BIG_ENDIAN 4321
#define __PDP_ENDIAN 3412
#define __BYTE_ORDER __LITTLE_ENDIAN
#else
#ifdef __APPLE__
#include <machine/endian.h>
#else
#ifdef __FreeBSD__
#include <sys/endian.h>
#else
#include <endian.h>
#endif
#endif
#endif
#endif
const char filePathSeparator =
#ifdef _WIN32
'\\';
#else
'/';
#endif
#define BUF_SIZE (16384*6)
#define DEFAULT_SAMPLE_RATE 2500000
//
#define DEFAULT_FFT_SIZE 2048
#define DEFAULT_DMOD_FFT_SIZE (DEFAULT_FFT_SIZE / 2)
#define DEFAULT_SCOPE_FFT_SIZE (DEFAULT_FFT_SIZE / 2)
//Both must be a power of 2 to prevent terrible OpenGL performance.
//TODO: Make the waterfall resolutions an option.
#define DEFAULT_MAIN_WATERFALL_LINES_NB 512 // 1024
#define DEFAULT_DEMOD_WATERFALL_LINES_NB 256
#define DEFAULT_DEMOD_TYPE "FM"
#define DEFAULT_DEMOD_BW 200000
#define DEFAULT_WATERFALL_LPS 30
//Dmod waterfall lines per second is adjusted
//so that the whole demod waterfall show DEMOD_WATERFALL_DURATION_IN_SECONDS
//seconds.
#define DEMOD_WATERFALL_DURATION_IN_SECONDS 4.0
#define CHANNELIZER_RATE_MAX 500000
#define MANUAL_SAMPLE_RATE_MIN 2000000 // 2MHz
#define MANUAL_SAMPLE_RATE_MAX 200000000 // 200MHz (We are 2017+ after all)
//Represents the amount of time to process in the FFT distributor.
#define FFT_DISTRIBUTOR_BUFFER_IN_SECONDS 0.250
//The maximum number of listed sample rates for a device, to be able to handle
//devices returning an insane amount because they have quasi-continuous ranges (UHD...)
#define DEVICE_SAMPLE_RATES_MAX_NB 25

View File

@ -0,0 +1,107 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "DemodLabelDialog.h"
#include "wx/clipbrd.h"
#include <sstream>
#include "CubicSDR.h"
wxBEGIN_EVENT_TABLE(DemodLabelDialog, wxDialog)
EVT_CHAR_HOOK(DemodLabelDialog::OnChar)
EVT_SHOW(DemodLabelDialog::OnShow)
wxEND_EVENT_TABLE()
DemodLabelDialog::DemodLabelDialog(wxWindow * parent, wxWindowID id, const wxString & title,
DemodulatorInstancePtr demod, const wxPoint & position,
const wxSize & size, long style) :
wxDialog(parent, id, title, position, size, style) {
wxString labelStr;
//by construction, is always != nullptr
activeDemod = demod;
labelStr = activeDemod->getDemodulatorUserLabel();
if (labelStr.empty()) {
//propose a default value...
labelStr = activeDemod->getDemodulatorType();
}
dialogText = new wxTextCtrl(this, wxID_LABEL_INPUT, labelStr, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER);
dialogText->SetFont(wxFont(15, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD));
// Set the textControl width to the [title + 100%] or the [content +100%],
// whichever's the greater.
int textCtrlX = dialogText->GetTextExtent(labelStr).GetWidth();
int titleX = this->GetTextExtent(title).GetWidth();
dialogText->SetMinSize(wxSize(max(int(2.0 * titleX), int(2.0 * textCtrlX)), -1));
auto* dialogsizer = new wxBoxSizer(wxALL);
dialogsizer->Add(dialogText, wxSizerFlags(1).Expand().Border(wxALL, 5));
SetSizerAndFit(dialogsizer);
Centre();
dialogText->SetSelection(-1, -1);
}
void DemodLabelDialog::OnChar(wxKeyEvent& event) {
int c = event.GetKeyCode();
//we support 16 bit strings for user labels internally.
wxString strValue = dialogText->GetValue();
switch (c) {
case WXK_RETURN:
case WXK_NUMPAD_ENTER:
//No need to display the demodulator type twice if the user do not change the default value...
//when comparing getDemodulatorType() std::string, take care of "upgrading" it to wxString which will
//try to its best...
if (strValue != wxString(activeDemod->getDemodulatorType())) {
activeDemod->setDemodulatorUserLabel(strValue.ToStdWstring());
}
else {
activeDemod->setDemodulatorUserLabel(L"");
}
wxGetApp().getBookmarkMgr().updateActiveList();
Close();
break;
case WXK_ESCAPE:
Close();
break;
}
if (event.ControlDown() && c == 'V') {
// Alter clipboard contents to remove unwanted chars
wxTheClipboard->Open();
wxTextDataObject data;
wxTheClipboard->GetData(data);
std::wstring clipText = data.GetText().ToStdWstring();
wxTheClipboard->SetData(new wxTextDataObject(clipText));
wxTheClipboard->Close();
event.Skip();
}
else if (c == WXK_RIGHT || c == WXK_LEFT || event.ControlDown()) {
event.Skip();
}
else {
#if defined(__linux__) || defined(__FreeBSD__)
dialogText->OnChar(event);
event.Skip();
#else
event.DoAllowNextEvent();
#endif
}
}
void DemodLabelDialog::OnShow(wxShowEvent &event) {
dialogText->SetFocus();
dialogText->SetSelection(-1, -1);
event.Skip();
}

View File

@ -0,0 +1,31 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "wx/dialog.h"
#include "wx/textctrl.h"
#include "wx/string.h"
#include "wx/button.h"
#include "DemodulatorInstance.h"
#define wxID_LABEL_INPUT 3002
class DemodLabelDialog : public wxDialog
{
public:
DemodLabelDialog( wxWindow * parent, wxWindowID id, const wxString & title,
DemodulatorInstancePtr demod = nullptr,
const wxPoint & pos = wxDefaultPosition,
const wxSize & size = wxDefaultSize,
long style = wxDEFAULT_DIALOG_STYLE);
wxTextCtrl * dialogText;
private:
DemodulatorInstancePtr activeDemod = nullptr;
void OnChar ( wxKeyEvent &event );
void OnShow(wxShowEvent &event);
DECLARE_EVENT_TABLE()
};

View File

@ -0,0 +1,266 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "FrequencyDialog.h"
#include "wx/clipbrd.h"
#include <sstream>
#include <algorithm>
#include "CubicSDR.h"
wxBEGIN_EVENT_TABLE(FrequencyDialog, wxDialog)
EVT_CHAR_HOOK(FrequencyDialog::OnChar)
EVT_SHOW(FrequencyDialog::OnShow)
wxEND_EVENT_TABLE()
FrequencyDialog::FrequencyDialog(wxWindow * parent, wxWindowID id, const wxString & title, DemodulatorInstancePtr demod, const wxPoint & /*position*/,
const wxSize & /*size*/, long style, FrequencyDialogTarget targetMode, wxString initString) :
wxDialog(parent, id, title, wxDefaultPosition, wxDefaultSize, style) {
wxString freqStr;
activeDemod = demod;
this->targetMode = targetMode;
this->initialString = initString;
if (targetMode == FDIALOG_TARGET_DEFAULT) {
if (activeDemod) {
freqStr = frequencyToStr(activeDemod->getFrequency());
} else {
freqStr = frequencyToStr(wxGetApp().getFrequency());
}
}
if (targetMode == FDIALOG_TARGET_BANDWIDTH) {
std::string lastDemodType = activeDemod?activeDemod->getDemodulatorType():wxGetApp().getDemodMgr().getLastDemodulatorType();
if (lastDemodType == "USB" || lastDemodType == "LSB") {
freqStr = frequencyToStr(wxGetApp().getDemodMgr().getLastBandwidth()/2);
} else {
freqStr = frequencyToStr(wxGetApp().getDemodMgr().getLastBandwidth());
}
}
if (targetMode == FDIALOG_TARGET_WATERFALL_LPS) {
freqStr = std::to_string(wxGetApp().getAppFrame()->getWaterfallDataThread()->getLinesPerSecond());
}
if (targetMode == FDIALOG_TARGET_SPECTRUM_AVG) {
freqStr = std::to_string(wxGetApp().getSpectrumProcessor()->getFFTAverageRate());
}
if (targetMode == FDIALOG_TARGET_GAIN) {
if (!wxGetApp().getActiveGainEntry().empty()) {
freqStr = std::to_string((int)wxGetApp().getGain(wxGetApp().getActiveGainEntry()));
}
}
dialogText = new wxTextCtrl(this, wxID_FREQ_INPUT, freqStr, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER);
dialogText->SetFont(wxFont(15, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD));
// Set the textControl width to the [title + 100%] or the [content +100%],
// whichever's the greater.
int textCtrlX = dialogText->GetTextExtent(freqStr).GetWidth();
int initTextCtrlX = dialogText->GetTextExtent(initString).GetWidth();
int titleX = this->GetTextExtent(title).GetWidth();
dialogText->SetMinSize(wxSize(max(int(2.0 * titleX), int(2.0 * std::max(textCtrlX, initTextCtrlX))), -1));
auto* dialogsizer = new wxBoxSizer(wxALL);
dialogsizer->Add(dialogText, wxSizerFlags(1).Expand().Border(wxALL, 5));
SetSizerAndFit(dialogsizer);
Centre();
if (!initString.empty() && initString.length() == 1) {
dialogText->SetValue(initString);
dialogText->SetSelection(2, 2);
dialogText->SetFocus();
} else {
if (!initString.empty()) {
dialogText->SetValue(initString);
}
dialogText->SetSelection(-1, -1);
}
}
void FrequencyDialog::OnChar(wxKeyEvent& event) {
int c = event.GetKeyCode();
long long freq, freq2, freq_ctr, range_bw;
double dblval;
std::string lastDemodType = activeDemod?activeDemod->getDemodulatorType():wxGetApp().getDemodMgr().getLastDemodulatorType();
std::string strValue = dialogText->GetValue().ToStdString();
bool ranged;
std::string strValue2;
size_t range_pos;
switch (c) {
case WXK_RETURN:
case WXK_NUMPAD_ENTER:
// Do Stuff
ranged = false;
if ((range_pos = strValue.find_first_of("-")) > 0) {
strValue2 = strValue.substr(range_pos+1);
strValue = strValue.substr(0,range_pos);
if (targetMode == FDIALOG_TARGET_DEFAULT && !activeDemod && strValue.length() && strValue2.length()) {
ranged = true;
}
}
if (targetMode == FDIALOG_TARGET_DEFAULT || targetMode == FDIALOG_TARGET_FREQ) {
if (ranged) {
freq = strToFrequency(strValue);
freq2 = strToFrequency(strValue2);
} else {
freq = strToFrequency(strValue);
}
if (activeDemod) {
activeDemod->setFrequency(freq);
activeDemod->updateLabel(freq);
freq_ctr = wxGetApp().getFrequency();
range_bw = wxGetApp().getSampleRate();
if (freq_ctr - (range_bw / 2) > freq || freq_ctr + (range_bw / 2) < freq) {
wxGetApp().setFrequency(freq);
}
} else {
if (ranged && (freq || freq2)) {
if (freq > freq2) {
std::swap(freq,freq2);
}
range_bw = (freq2-freq);
freq_ctr = freq + (range_bw/2);
if (range_bw > wxGetApp().getSampleRate()) {
range_bw = wxGetApp().getSampleRate();
}
if (range_bw < 30000) {
range_bw = 30000;
}
if (freq == freq2) {
wxGetApp().setFrequency(freq_ctr);
wxGetApp().getAppFrame()->setViewState();
} else {
if (wxGetApp().getSampleRate()/4 > range_bw) {
wxGetApp().setFrequency(freq_ctr + wxGetApp().getSampleRate()/4);
} else {
wxGetApp().setFrequency(freq_ctr);
}
wxGetApp().getAppFrame()->setViewState(freq_ctr, range_bw);
}
} else {
wxGetApp().setFrequency(freq);
}
}
}
if (targetMode == FDIALOG_TARGET_BANDWIDTH) {
freq = strToFrequency(strValue);
if (lastDemodType == "USB" || lastDemodType == "LSB") {
freq *= 2;
}
if (freq > CHANNELIZER_RATE_MAX) {
freq = CHANNELIZER_RATE_MAX;
}
if (activeDemod) {
activeDemod->setBandwidth(freq);
} else {
wxGetApp().getDemodMgr().setLastBandwidth(freq);
}
}
if (targetMode == FDIALOG_TARGET_WATERFALL_LPS) {
try {
freq = std::stoi(strValue);
} catch (const exception &) {
Close();
break;
}
if (freq > 1024) {
freq = 1024;
}
if (freq < 1) {
freq = 1;
}
wxGetApp().getAppFrame()->setWaterfallLinesPerSecond(freq);
}
if (targetMode == FDIALOG_TARGET_SPECTRUM_AVG) {
try {
dblval = std::stod(strValue);
} catch (const exception &) {
Close();
break;
}
if (dblval > 0.99) {
dblval = 0.99;
}
if (dblval < 0.1) {
dblval = 0.1;
}
wxGetApp().getAppFrame()->setSpectrumAvgSpeed(dblval);
}
if (targetMode == FDIALOG_TARGET_GAIN) {
try {
freq = std::stoi(strValue);
} catch (const exception &) {
break;
}
SDRDeviceInfo *devInfo = wxGetApp().getDevice();
std::string gainName = wxGetApp().getActiveGainEntry();
if (gainName.empty()) {
break;
}
SDRRangeMap gains = devInfo->getGains(SOAPY_SDR_RX, 0);
if (freq > gains[gainName].maximum()) {
freq = gains[gainName].maximum();
}
if (freq < gains[gainName].minimum()) {
freq = gains[gainName].minimum();
}
wxGetApp().setGain(gainName, freq);
wxGetApp().getAppFrame()->refreshGainUI();
}
Close();
break;
case WXK_ESCAPE:
Close();
break;
}
std::string allowed("0123456789.MKGHZmkghz");
// Support '-' for range
if (targetMode == FDIALOG_TARGET_DEFAULT && !activeDemod && strValue.length() > 0) {
allowed.append("-");
}
if (allowed.find_first_of(c) != std::string::npos || c == WXK_DELETE || c == WXK_BACK || c == WXK_NUMPAD_DECIMAL
|| (c >= WXK_NUMPAD0 && c <= WXK_NUMPAD9)) {
#if defined(__linux__) || defined(__FreeBSD__)
dialogText->OnChar(event);
event.Skip();
#else
event.DoAllowNextEvent();
#endif
} else if (event.ControlDown() && c == 'V') {
// Alter clipboard contents to remove unwanted chars
wxTheClipboard->Open();
wxTextDataObject data;
wxTheClipboard->GetData(data);
std::string clipText = data.GetText().ToStdString();
std::string pasteText = filterChars(clipText, std::string(allowed));
wxTheClipboard->SetData(new wxTextDataObject(pasteText));
wxTheClipboard->Close();
event.Skip();
} else if (c == WXK_RIGHT || c == WXK_LEFT || event.ControlDown()) {
event.Skip();
}
}
void FrequencyDialog::OnShow(wxShowEvent &event) {
if (initialString.length() == 1) {
dialogText->SetFocus();
dialogText->SetSelection(2, 2);
}
event.Skip();
}

View File

@ -0,0 +1,43 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "wx/dialog.h"
#include "wx/textctrl.h"
#include "wx/string.h"
#include "wx/button.h"
#include "DemodulatorInstance.h"
#define wxID_FREQ_INPUT 3001
class FrequencyDialog: public wxDialog
{
public:
typedef enum FrequencyDialogTarget {
FDIALOG_TARGET_DEFAULT,
FDIALOG_TARGET_CENTERFREQ,
FDIALOG_TARGET_FREQ,
FDIALOG_TARGET_BANDWIDTH,
FDIALOG_TARGET_WATERFALL_LPS,
FDIALOG_TARGET_SPECTRUM_AVG,
FDIALOG_TARGET_GAIN
} FrequencyDialogTarget;
FrequencyDialog ( wxWindow * parent, wxWindowID id, const wxString & title,
DemodulatorInstancePtr demod = nullptr,
const wxPoint & pos = wxDefaultPosition,
const wxSize & size = wxDefaultSize,
long style = wxDEFAULT_DIALOG_STYLE,
FrequencyDialogTarget targetMode = FDIALOG_TARGET_DEFAULT,
wxString initString = "");
wxTextCtrl * dialogText;
private:
DemodulatorInstancePtr activeDemod;
void OnChar ( wxKeyEvent &event );
void OnShow(wxShowEvent &event);
FrequencyDialogTarget targetMode;
std::string initialString;
DECLARE_EVENT_TABLE()
};

View File

@ -0,0 +1,132 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "IOThread.h"
#include <memory>
#define SPIN_WAIT_SLEEP_MS 5
IOThread::IOThread() {
terminated.store(false);
stopping.store(false);
}
IOThread::~IOThread() {
terminated.store(true);
stopping.store(true);
}
#ifdef __APPLE__
void *IOThread::threadMain() {
terminated.store(false);
stopping.store(false);
try {
run();
}
catch (...) {
terminated.store(true);
stopping.store(true);
throw;
}
terminated.store(true);
stopping.store(true);
return this;
};
void *IOThread::pthread_helper(void *context) {
return ((IOThread *) context)->threadMain();
};
#else
void IOThread::threadMain() {
terminated.store(false);
stopping.store(false);
try {
run();
}
catch (...) {
terminated.store(true);
stopping.store(true);
throw;
}
terminated.store(true);
stopping.store(true);
}
#endif
void IOThread::setup() {
//redefined in subclasses
}
void IOThread::run() {
//redefined in subclasses
}
void IOThread::terminate() {
stopping.store(true);
}
void IOThread::onBindOutput(std::string /* name */, ThreadQueueBasePtr /* threadQueue */) {
}
void IOThread::onBindInput(std::string /* name */, ThreadQueueBasePtr /* threadQueue */) {
}
void IOThread::setInputQueue(const std::string& qname, const ThreadQueueBasePtr& threadQueue) {
std::lock_guard < std::mutex > lock(m_queue_bindings_mutex);
input_queues[qname] = threadQueue;
this->onBindInput(qname, threadQueue);
}
ThreadQueueBasePtr IOThread::getInputQueue(const std::string& qname) {
std::lock_guard < std::mutex > lock(m_queue_bindings_mutex);
return input_queues[qname];
}
void IOThread::setOutputQueue(const std::string& qname, const ThreadQueueBasePtr& threadQueue) {
std::lock_guard < std::mutex > lock(m_queue_bindings_mutex);
output_queues[qname] = threadQueue;
this->onBindOutput(qname, threadQueue);
}
ThreadQueueBasePtr IOThread::getOutputQueue(const std::string& qname) {
std::lock_guard < std::mutex > lock(m_queue_bindings_mutex);
return output_queues[qname];
}
bool IOThread::isTerminated(int waitMs) {
if (terminated.load()) {
return true;
}
else if (waitMs == 0) {
return false;
}
//this is a stupid busy plus sleep loop
int nbCyclesToWait;
if (waitMs < 0) {
nbCyclesToWait = std::numeric_limits<int>::max();
}
else {
nbCyclesToWait = (waitMs / SPIN_WAIT_SLEEP_MS) + 1;
}
for ( int i = 0; i < nbCyclesToWait; i++) {
std::this_thread::sleep_for(std::chrono::milliseconds(SPIN_WAIT_SLEEP_MS));
if (terminated.load()) {
return true;
}
}
std::cout << "ERROR: thread '" << typeid(*this).name() << "' has not terminated in time ! (> " << waitMs << " ms)" << std::endl << std::flush;
return terminated.load();
}

View File

@ -0,0 +1,209 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include <mutex>
#include <atomic>
#include <deque>
#include <map>
#include <set>
#include <string>
#include <iostream>
#include <thread>
#include <memory>
#include <climits>
#include "ThreadBlockingQueue.h"
#include "Timer.h"
#include "SpinMutex.h"
struct map_string_less
{
bool operator()(const std::string& a,const std::string& b) const
{
return a.compare(b) < 0;
}
};
template <typename PtrType>
class ReBufferAge {
public:
ReBufferAge(PtrType p, int a) {
ptr = p;
age = a;
}
PtrType ptr;
int age;
virtual ~ReBufferAge() = default;;
};
#define REBUFFER_GC_LIMIT 100
#define REBUFFER_WARNING_THRESHOLD 2000
template<typename BufferType>
class ReBuffer {
typedef typename std::shared_ptr<BufferType> ReBufferPtr;
public:
//Virtual destructor to assure correct freeing of all descendants.
virtual ~ReBuffer() = default;
//constructor
explicit ReBuffer(std::string bufferId) : bufferId(bufferId) {
//nothing
}
/// Return a new ReBuffer_ptr usable by the application.
ReBufferPtr getBuffer() {
std::lock_guard < SpinMutex > lock(m_mutex);
// iterate the ReBufferAge list: if the std::shared_ptr count == 1, it means
//it is only referenced in outputBuffers itself, so available for re-use.
//else if the std::shared_ptr count <= 1, make it age.
//else the ReBufferPtr is in use, don't use it.
ReBufferPtr buf = nullptr;
outputBuffersI it = outputBuffers.begin();
while (it != outputBuffers.end()) {
//careful here: take care of reading the use_count directly
//through the iterator, else it's value is wrong if a temp variable
//is used.
long use = it->ptr.use_count();
//1. If we encounter a ReBufferPtr with a use count of 0, this
//is a bug since it is supposed to be at least 1, because it is referenced here.
//in this case, purge it from here and trace.
if (use == 0) {
std::cout << "Warning: in ReBuffer '" << bufferId << "' count '" << outputBuffers.size() << "', found 1 dangling buffer !" << std::endl << std::flush;
it = outputBuffers.erase(it);
}
else if (use == 1) {
if (buf == nullptr) {
it->age = 1; //select this one.
buf = it->ptr;
// std::cout << "**" << std::flush;
it++;
}
else {
//make the other unused buffers age
it->age--;
it++;
}
}
else {
it++;
}
} //end while
//2.1 Garbage collect the oldest (last element) if it aged too much, and return the buffer
if (buf != nullptr) {
if (outputBuffers.back().age < -REBUFFER_GC_LIMIT) {
//by the nature of the shared_ptr, memory will ne deallocated automatically.
outputBuffers.pop_back();
//std::cout << "--" << std::flush;
}
return buf;
}
if (outputBuffers.size() > REBUFFER_WARNING_THRESHOLD) {
std::cout << "Warning: ReBuffer '" << bufferId << "' count '" << outputBuffers.size() << "' exceeds threshold of '" << REBUFFER_WARNING_THRESHOLD << "'" << std::endl << std::flush;
}
//3.We need to allocate a new buffer.
ReBufferAge < ReBufferPtr > newBuffer(std::make_shared<BufferType>(), 1);
outputBuffers.push_back(newBuffer);
// std::cout << "++" << std::flush;
return newBuffer.ptr;
}
/// Purge the cache.
void purge() {
std::lock_guard < SpinMutex > lock(m_mutex);
// since outputBuffers are full std::shared_ptr,
//purging if will effectively loose the local reference,
// so the std::shared_ptr will naturally be deallocated
//when their time comes.
outputBuffers.clear();
}
private:
//name of the buffer cache kind
std::string bufferId;
//the ReBuffer cache: use a std:deque to also release
//memory when ReBufferPtr are GCed.
std::deque< ReBufferAge < ReBufferPtr > > outputBuffers;
typedef typename std::deque< ReBufferAge < ReBufferPtr > >::iterator outputBuffersI;
//mutex protecting access to outputBuffers.
SpinMutex m_mutex;
};
class IOThread {
public:
IOThread();
virtual ~IOThread();
static void *pthread_helper(void *context);
#ifdef __APPLE__
virtual void *threadMain();
#else
//the thread Main call back itself
virtual void threadMain();
#endif
virtual void setup();
virtual void run();
//Request for termination (asynchronous)
virtual void terminate();
//Returns true if the thread is indeed terminated, i.e the run() method
//has returned.
//If wait > 0 ms, the call is blocking at most 'waitMs' milliseconds for the thread to die, then returns.
//If wait < 0, the wait in infinite until the thread dies.
bool isTerminated(int waitMs = 0);
virtual void onBindOutput(std::string name, ThreadQueueBasePtr threadQueue);
virtual void onBindInput(std::string name, ThreadQueueBasePtr threadQueue);
void setInputQueue(const std::string& qname, const ThreadQueueBasePtr& threadQueue);
ThreadQueueBasePtr getInputQueue(const std::string& qname);
void setOutputQueue(const std::string& qname, const ThreadQueueBasePtr& threadQueue);
ThreadQueueBasePtr getOutputQueue(const std::string& qname);
protected:
std::map<std::string, ThreadQueueBasePtr, map_string_less> input_queues;
std::map<std::string, ThreadQueueBasePtr, map_string_less> output_queues;
//this protects against concurrent changes in input/output bindings: get/set/Input/OutPutQueue
std::mutex m_queue_bindings_mutex;
//true when a termination is ordered
std::atomic_bool stopping;
Timer gTimer;
private:
//true when the thread has really ended, i.e run() from threadMain() has returned.
std::atomic_bool terminated;
};

View File

@ -0,0 +1,299 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "ModemProperties.h"
#include "CubicSDR.h"
ModemProperties::ModemProperties(wxWindow *parent, wxWindowID winid,
const wxPoint& pos, const wxSize& size, long style, const wxString& name) : wxPanel(parent, winid, pos, size, style, name) {
m_propertyGrid = new wxPropertyGrid(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxPG_DEFAULT_STYLE);
bSizer = new wxBoxSizer( wxVERTICAL );
bSizer->Add(m_propertyGrid, 1, wxEXPAND, 0);
this->SetSizer(bSizer);
m_propertyGrid->Connect( wxEVT_PG_ITEM_COLLAPSED, wxPropertyGridEventHandler( ModemProperties::OnCollapse ), nullptr, this );
m_propertyGrid->Connect( wxEVT_PG_ITEM_EXPANDED, wxPropertyGridEventHandler( ModemProperties::OnExpand ), nullptr, this );
m_propertyGrid->Connect( wxEVT_PG_CHANGED, wxPropertyGridEventHandler( ModemProperties::OnChange ), nullptr, this );
this->Connect( wxEVT_SHOW, wxShowEventHandler( ModemProperties::OnShow ), nullptr, this );
this->Connect( wxEVT_ENTER_WINDOW, wxMouseEventHandler( ModemProperties::OnMouseEnter ), nullptr, this);
this->Connect( wxEVT_LEAVE_WINDOW, wxMouseEventHandler( ModemProperties::OnMouseLeave ), nullptr, this);
updateTheme();
mouseInView = false;
collapsed = false;
}
void ModemProperties::OnShow(wxShowEvent & /* event */) {
}
void ModemProperties::updateTheme() {
wxColour bgColor(ThemeMgr::mgr.currentTheme->generalBackground);
wxColour textColor(ThemeMgr::mgr.currentTheme->text);
wxColour btn(ThemeMgr::mgr.currentTheme->button);
wxColour btnHl(ThemeMgr::mgr.currentTheme->buttonHighlight);
m_propertyGrid->SetEmptySpaceColour(bgColor);
m_propertyGrid->SetCellBackgroundColour(bgColor);
m_propertyGrid->SetCellTextColour(textColor);
m_propertyGrid->SetSelectionTextColour(bgColor);
m_propertyGrid->SetSelectionBackgroundColour(btnHl);
m_propertyGrid->SetCaptionTextColour(bgColor);
m_propertyGrid->SetCaptionBackgroundColour(btn);
m_propertyGrid->SetLineColour(btn);
}
ModemProperties::~ModemProperties() = default;
void ModemProperties::initDefaultProperties() {
if (audioOutputDevices.empty()) {
std::vector<string> outputOpts;
std::vector<string> outputOptNames;
AudioThread::enumerateDevices(audioDevices);
int i = 0;
for (const auto& aDev : audioDevices) {
if (aDev.inputChannels) {
audioInputDevices[i] = aDev;
}
if (aDev.outputChannels) {
audioOutputDevices[i] = aDev;
}
i++;
}
// int defaultDevice = 0;
// int dc = 0;
for (const auto& mdevices_i : audioOutputDevices) {
outputOpts.push_back(std::to_string(mdevices_i.first));
outputOptNames.push_back(mdevices_i.second.name);
// if (mdevices_i.second.isDefaultOutput) {
// defaultDevice = dc;
// }
// dc++;
}
outputArg.key ="._audio_output";
outputArg.name = "Audio Out";
outputArg.description = "Set the current modem's audio output device.";
outputArg.type = ModemArgInfo::Type::STRING;
outputArg.options = outputOpts;
outputArg.optionNames = outputOptNames;
}
int currentOutput = demodContext->getOutputDevice();
outputArg.value = std::to_string(currentOutput);
defaultProps["._audio_output"] = addArgInfoProperty(m_propertyGrid, outputArg);
}
void ModemProperties::initProperties(ModemArgInfoList newArgs, const DemodulatorInstancePtr& demodInstance) {
args = newArgs;
demodContext = demodInstance;
bSizer->Layout();
m_propertyGrid->Clear();
if (!demodInstance) {
m_propertyGrid->Append(new wxPropertyCategory("Modem Settings"));
return;
}
m_propertyGrid->Append(new wxPropertyCategory(demodInstance->getDemodulatorType() + " Settings"));
initDefaultProperties();
ModemArgInfoList::const_iterator args_i;
for (args_i = args.begin(); args_i != args.end(); args_i++) {
ModemArgInfo arg = (*args_i);
props[arg.key] = addArgInfoProperty(m_propertyGrid, arg);
}
m_propertyGrid->FitColumns();
if (collapsed) {
m_propertyGrid->CollapseAll();
}
}
wxPGProperty *ModemProperties::addArgInfoProperty(wxPropertyGrid *pg, ModemArgInfo arg) {
wxPGProperty *prop = nullptr;
int intVal;
double floatVal;
std::vector<std::string>::iterator stringIter;
switch (arg.type) {
case ModemArgInfo::Type::INT:
try {
intVal = std::stoi(arg.value);
} catch (const std::invalid_argument &) {
intVal = 0;
}
prop = pg->Append( new wxIntProperty(arg.name, wxPG_LABEL, intVal) );
if (arg.range.minimum() != arg.range.maximum()) {
pg->SetPropertyAttribute( prop, wxPG_ATTR_MIN, arg.range.minimum());
pg->SetPropertyAttribute( prop, wxPG_ATTR_MAX, arg.range.maximum());
}
break;
case ModemArgInfo::Type::FLOAT:
try {
floatVal = std::stod(arg.value);
} catch (const std::invalid_argument &) {
floatVal = 0;
}
prop = pg->Append( new wxFloatProperty(arg.name, wxPG_LABEL, floatVal) );
if (arg.range.minimum() != arg.range.maximum()) {
pg->SetPropertyAttribute( prop, wxPG_ATTR_MIN, arg.range.minimum());
pg->SetPropertyAttribute( prop, wxPG_ATTR_MAX, arg.range.maximum());
}
break;
case ModemArgInfo::Type::BOOL:
prop = pg->Append( new wxBoolProperty(arg.name, wxPG_LABEL, (arg.value=="true")) );
break;
case ModemArgInfo::Type::STRING:
if (!arg.options.empty()) {
intVal = 0;
prop = pg->Append( new wxEnumProperty(arg.name, wxPG_LABEL) );
for (stringIter = arg.options.begin(); stringIter != arg.options.end(); stringIter++) {
std::string optName = (*stringIter);
std::string displayName = optName;
if (!arg.optionNames.empty()) {
displayName = arg.optionNames[intVal];
}
prop->AddChoice(displayName);
if ((*stringIter)==arg.value) {
prop->SetChoiceSelection(intVal);
}
intVal++;
}
} else {
prop = pg->Append( new wxStringProperty(arg.name, wxPG_LABEL, arg.value) );
}
break;
case ModemArgInfo::Type::PATH_DIR:
break;
case ModemArgInfo::Type::PATH_FILE:
break;
case ModemArgInfo::Type::COLOR:
break;
}
if (prop != nullptr) {
prop->SetHelpString(arg.name + ": " + arg.description);
}
return prop;
}
std::string ModemProperties::readProperty(const std::string& key) {
int i = 0;
ModemArgInfoList::const_iterator args_i;
for (args_i = args.begin(); args_i != args.end(); args_i++) {
ModemArgInfo arg = (*args_i);
if (arg.key == key) {
wxPGProperty *prop = props[key];
std::string result;
if (arg.type == ModemArgInfo::Type::STRING && !arg.options.empty()) {
return arg.options[prop->GetChoiceSelection()];
} else if (arg.type == ModemArgInfo::Type::BOOL) {
return (prop->GetValueAsString()=="True")?"true":"false";
} else {
return prop->GetValueAsString().ToStdString();
}
}
i++;
}
return "";
}
void ModemProperties::OnChange(wxPropertyGridEvent &event) {
if (!demodContext || !demodContext->isActive()) {
return;
}
std::map<std::string, wxPGProperty *>::const_iterator prop_i;
if (event.m_property == defaultProps["._audio_output"]) {
int sel = event.m_property->GetChoiceSelection();
outputArg.value = outputArg.options[sel];
if (demodContext) {
try {
demodContext->setOutputDevice(std::stoi(outputArg.value));
} catch (const exception &) {
// .. this should never happen ;)
}
wxGetApp().getAppFrame()->setScopeDeviceName(outputArg.optionNames[sel]);
}
return;
}
for (prop_i = props.begin(); prop_i != props.end(); prop_i++) {
if (prop_i->second == event.m_property) {
std::string key = prop_i->first;
std::string value = readProperty(prop_i->first);
demodContext->writeModemSetting(key, value);
return;
}
}
}
void ModemProperties::OnCollapse(wxPropertyGridEvent & /* event */) {
collapsed = true;
}
void ModemProperties::OnExpand(wxPropertyGridEvent &/* event */) {
collapsed = false;
}
void ModemProperties::OnMouseEnter(wxMouseEvent & /* event */) {
mouseInView = true;
}
void ModemProperties::OnMouseLeave(wxMouseEvent & /* event */) {
mouseInView = false;
}
bool ModemProperties::isMouseInView() {
return mouseInView || (m_propertyGrid && m_propertyGrid->IsEditorFocused());
}
void ModemProperties::setCollapsed(bool state) {
collapsed = state;
if (m_propertyGrid) {
if (state) {
m_propertyGrid->CollapseAll();
} else {
m_propertyGrid->ExpandAll();
}
}
}
bool ModemProperties::isCollapsed() const {
return collapsed;
}
void ModemProperties::fitColumns() {
m_propertyGrid->FitColumns();
}

View File

@ -0,0 +1,61 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include <wx/panel.h>
#include <wx/sizer.h>
#include <wx/propgrid/propgrid.h>
#include <wx/propgrid/advprops.h>
#include "DemodulatorInstance.h"
#include "Modem.h"
class ModemProperties : public wxPanel {
public:
explicit ModemProperties(
wxWindow *parent,
wxWindowID winid = wxID_ANY,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxTAB_TRAVERSAL | wxNO_BORDER,
const wxString& name = wxPanelNameStr
);
~ModemProperties() override;
void initDefaultProperties();
void initProperties(ModemArgInfoList newArgs, const DemodulatorInstancePtr& demodInstance);
bool isMouseInView();
void setCollapsed(bool state);
bool isCollapsed() const;
void fitColumns();
void updateTheme();
private:
wxPGProperty *addArgInfoProperty(wxPropertyGrid *pg, ModemArgInfo arg);
std::string readProperty(const std::string&);
void OnChange(wxPropertyGridEvent &event);
void OnShow(wxShowEvent &event);
void OnCollapse(wxPropertyGridEvent &event);
void OnExpand(wxPropertyGridEvent &event);
void OnMouseEnter(wxMouseEvent &event);
void OnMouseLeave(wxMouseEvent &event);
wxBoxSizer* bSizer;
wxPropertyGrid* m_propertyGrid;
ModemArgInfoList args;
DemodulatorInstancePtr demodContext;
std::map<std::string, wxPGProperty *> props;
bool mouseInView, collapsed;
ModemArgInfoList defaultArgs;
ModemArgInfo outputArg;
std::map<std::string, wxPGProperty *> defaultProps;
std::vector<RtAudio::DeviceInfo> audioDevices;
std::map<int,RtAudio::DeviceInfo> audioInputDevices;
std::map<int,RtAudio::DeviceInfo> audioOutputDevices;
};

View File

@ -0,0 +1,195 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "SessionMgr.h"
#include "CubicSDR.h"
void SessionMgr::saveSession(std::string fileName) {
DataTree s("cubicsdr_session");
DataNode *header = s.rootNode()->newChild("header");
//save as wstring to prevent problems
header->newChild("version")->element()->set(wxString(CUBICSDR_VERSION).ToStdWstring());
*header->newChild("center_freq") = wxGetApp().getFrequency();
*header->newChild("sample_rate") = wxGetApp().getSampleRate();
*header->newChild("solo_mode") = wxGetApp().getSoloMode()?1:0;
WaterfallCanvas *waterfallCanvas = wxGetApp().getAppFrame()->getWaterfallCanvas();
if (waterfallCanvas->getViewState()) {
DataNode *viewState = header->newChild("view_state");
*viewState->newChild("center_freq") = waterfallCanvas->getCenterFrequency();
*viewState->newChild("bandwidth") = waterfallCanvas->getBandwidth();
}
DataNode *demods = s.rootNode()->newChild("demodulators");
//make a local copy snapshot of the list
std::vector<DemodulatorInstancePtr> instances = wxGetApp().getDemodMgr().getDemodulators();
for (const auto &instance : instances) {
DataNode *demod = demods->newChild("demodulator");
wxGetApp().getDemodMgr().saveInstance(demod, instance);
} //end for demodulators
// Make sure the file name actually ends in .xml
std::string lcFileName = fileName;
std::transform(lcFileName.begin(), lcFileName.end(), lcFileName.begin(), ::tolower);
if (lcFileName.find_last_of(".xml") != lcFileName.length()-1) {
fileName.append(".xml");
}
s.SaveToFileXML(fileName);
}
bool SessionMgr::loadSession(const std::string& fileName) {
DataTree l;
if (!l.LoadFromFileXML(fileName)) {
return false;
}
//Check if it is a session file, read the root node.
if (l.rootNode()->getName() != "cubicsdr_session") {
return false;
}
wxGetApp().getDemodMgr().setActiveDemodulator(nullptr, false);
wxGetApp().getDemodMgr().terminateAll();
WaterfallCanvas *waterfallCanvas = wxGetApp().getAppFrame()->getWaterfallCanvas();
SpectrumCanvas *spectrumCanvas = wxGetApp().getAppFrame()->getSpectrumCanvas();
try {
if (!l.rootNode()->hasAnother("header")) {
return false;
}
DataNode *header = l.rootNode()->getNext("header");
if (header->hasAnother("version")) {
//"Force" the retrieving of the value as string, even if it looks like a number internally ! (ex: "0.2.0")
DataNode *versionNode = header->getNext("version");
std::wstring version;
try {
versionNode->element()->get(version);
std::cout << "Loading session file version: '" << version << "'..." << std::endl;
}
catch (DataTypeMismatchException &e) {
//this is for managing the old session format NOT encoded as std:wstring,
//force current version
std::cout << "Warning while Loading session file version, probably old format :'" << e.what() << "' please consider re-saving the current session..." << std::endl << std::flush;
version = wxString(CUBICSDR_VERSION).ToStdWstring();
}
}
if (header->hasAnother("sample_rate")) {
long sample_rate = (long)*header->getNext("sample_rate");
SDRDeviceInfo *dev = wxGetApp().getSDRThread()->getDevice();
if (dev) {
//retrieve the available sample rates. A valid previously chosen manual
//value is constrained within these limits. If it doesn't behave, lets the device choose
//for us.
long minRate = MANUAL_SAMPLE_RATE_MIN;
long maxRate = MANUAL_SAMPLE_RATE_MAX;
std::vector<long> sampleRates = dev->getSampleRates(SOAPY_SDR_RX, 0);
if (!sampleRates.empty()) {
minRate = sampleRates.front();
maxRate = sampleRates.back();
}
//If it is beyond limits, make device choose a reasonable value
if (sample_rate < minRate || sample_rate > maxRate) {
sample_rate = dev->getSampleRateNear(SOAPY_SDR_RX, 0, sample_rate);
}
//update applied value
wxGetApp().setSampleRate(sample_rate);
} else {
wxGetApp().setSampleRate(sample_rate);
}
}
if (header->hasAnother("solo_mode")) {
int solo_mode_activated = (int)*header->getNext("solo_mode");
wxGetApp().setSoloMode(solo_mode_activated > 0);
}
else {
wxGetApp().setSoloMode(false);
}
DemodulatorInstancePtr loadedActiveDemod = nullptr;
DemodulatorInstancePtr newDemod = nullptr;
if (l.rootNode()->hasAnother("demodulators")) {
DataNode *demodulators = l.rootNode()->getNext("demodulators");
std::vector<DemodulatorInstancePtr> demodsLoaded;
while (demodulators->hasAnother("demodulator")) {
DataNode *demod = demodulators->getNext("demodulator");
if (!demod->hasAnother("bandwidth") || !demod->hasAnother("frequency")) {
continue;
}
newDemod = wxGetApp().getDemodMgr().loadInstance(demod);
if (demod->hasAnother("active")) {
loadedActiveDemod = newDemod;
}
newDemod->run();
newDemod->setActive(true);
demodsLoaded.push_back(newDemod);
}
if (!demodsLoaded.empty()) {
wxGetApp().notifyDemodulatorsChanged();
}
} // if l.rootNode()->hasAnother("demodulators")
if (header->hasAnother("center_freq")) {
long long center_freq = (long long)*header->getNext("center_freq");
wxGetApp().setFrequency(center_freq);
// std::cout << "\tCenter Frequency: " << center_freq << std::endl;
}
if (header->hasAnother("view_state")) {
DataNode *viewState = header->getNext("view_state");
if (viewState->hasAnother("center_freq") && viewState->hasAnother("bandwidth")) {
auto center_freq = (long long)*viewState->getNext("center_freq");
auto bandwidth = (int)*viewState->getNext("bandwidth");
spectrumCanvas->setView(center_freq, bandwidth);
waterfallCanvas->setView(center_freq, bandwidth);
}
} else {
spectrumCanvas->disableView();
waterfallCanvas->disableView();
spectrumCanvas->setCenterFrequency(wxGetApp().getFrequency());
waterfallCanvas->setCenterFrequency(wxGetApp().getFrequency());
}
if (loadedActiveDemod || newDemod) {
wxGetApp().getDemodMgr().setActiveDemodulator(loadedActiveDemod?loadedActiveDemod:newDemod, false);
}
} catch (DataTypeMismatchException &e) {
std::cout << e.what() << std::endl;
return false;
}
return true;
}

View File

@ -0,0 +1,14 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "DataTree.h"
#include "AppFrame.h"
class SessionMgr {
public:
void saveSession(std::string fileName);
bool loadSession(const std::string& fileName);
};

View File

@ -0,0 +1,46 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "AudioFile.h"
#include "CubicSDR.h"
#include <sstream>
AudioFile::AudioFile() = default;
AudioFile::~AudioFile() = default;
void AudioFile::setOutputFileName(std::string filename) {
filenameBase = filename;
}
std::string AudioFile::getOutputFileName() {
std::string recPath = wxGetApp().getConfig()->getRecordingPath();
// Strip any invalid characters from the name
std::string stripChars("<>:\"/\\|?*");
std::string filenameBaseSafe = filenameBase;
for (size_t i = 0, iMax = filenameBaseSafe.length(); i < iMax; i++) {
if (stripChars.find(filenameBaseSafe[i]) != std::string::npos) {
filenameBaseSafe.replace(i,1,"_");
}
}
// Create output file name
std::stringstream outputFileName;
outputFileName << recPath << filePathSeparator << filenameBaseSafe;
int idx = 0;
// If the file exists; then find the next non-existing file in sequence.
std::string fileNameCandidate = outputFileName.str();
while (FILE *file = fopen((fileNameCandidate + "." + getExtension()).c_str(), "r")) {
fclose(file);
fileNameCandidate = outputFileName.str() + "-" + std::to_string(++idx);
}
return fileNameCandidate + "." + getExtension();
}

View File

@ -0,0 +1,25 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "AudioThread.h"
class AudioFile
{
public:
AudioFile();
virtual ~AudioFile();
virtual void setOutputFileName(std::string filename);
virtual std::string getExtension() = 0;
virtual std::string getOutputFileName();
virtual bool writeToFile(AudioThreadInputPtr input) = 0;
virtual bool closeFile() = 0;
protected:
std::string filenameBase;
};

View File

@ -0,0 +1,219 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "AudioFileWAV.h"
#include "CubicSDR.h"
#include <iomanip>
//limit file size to 2GB (- margin) for maximum compatibility.
#define MAX_WAV_FILE_SIZE (0x7FFFFFFF - 1024)
// Simple endian io read/write handling from
// http://www.cplusplus.com/forum/beginner/31584/#msg171056
namespace little_endian_io
{
template <typename Word>
std::ostream& write_word(std::ostream& outs, Word value, unsigned size = sizeof(Word)) {
for (; size; --size, value >>= 8) {
outs.put(static_cast <char> (value & 0xFF));
}
return outs;
}
template <typename Word>
std::istream& read_word(std::istream& ins, Word& value, unsigned size = sizeof(Word)) {
value = 0;
for (unsigned n = 0; n < size; ++n) {
value |= ins.get() << (8 * n);
}
return ins;
}
}
namespace big_endian_io
{
template <typename Word>
std::ostream& write_word(std::ostream& outs, Word value, unsigned size = sizeof(Word)) {
while (size) {
outs.put(static_cast <char> ((value >> (8 * --size)) & 0xFF));
}
return outs;
}
template <typename Word>
std::istream& read_word(std::istream& ins, Word& value, unsigned size = sizeof(Word)) {
for (value = 0; size; --size) {
value = (value << 8) | ins.get();
}
return ins;
}
}
using namespace little_endian_io;
AudioFileWAV::AudioFileWAV() : AudioFile(), dataChunkPos(0) {
}
AudioFileWAV::~AudioFileWAV() = default;
std::string AudioFileWAV::getExtension()
{
return "wav";
}
bool AudioFileWAV::writeToFile(AudioThreadInputPtr input)
{
if (!outputFileStream.is_open()) {
std::string ofName = getOutputFileName();
outputFileStream.open(ofName.c_str(), std::ios::binary);
currentFileSize = 0;
writeHeaderToFileStream(input);
}
size_t maxRoomInCurrentFileInSamples = getMaxWritableNumberOfSamples(input);
if (maxRoomInCurrentFileInSamples >= input->data.size()) {
writePayloadToFileStream(input, 0, input->data.size());
}
else {
//we complete the current file and open another:
writePayloadToFileStream(input, 0, maxRoomInCurrentFileInSamples);
closeFile();
// Open a new file with the next sequence number, and dump the rest of samples in it.
currentSequenceNumber++;
currentFileSize = 0;
std::string ofName = getOutputFileName();
outputFileStream.open(ofName.c_str(), std::ios::binary);
writeHeaderToFileStream(input);
writePayloadToFileStream(input, maxRoomInCurrentFileInSamples, input->data.size());
}
return true;
}
bool AudioFileWAV::closeFile()
{
if (outputFileStream.is_open()) {
size_t file_length = outputFileStream.tellp();
// Fix the data chunk header to contain the data size
outputFileStream.seekp(dataChunkPos + 4);
write_word(outputFileStream, file_length - (dataChunkPos + 8), 4);
// Fix the file header to contain the proper RIFF chunk size, which is (file size - 8) bytes
outputFileStream.seekp(0 + 4);
write_word(outputFileStream, file_length - 8, 4);
outputFileStream.close();
currentFileSize = 0;
}
return true;
}
void AudioFileWAV::writeHeaderToFileStream(const AudioThreadInputPtr& input) {
// Based on simple wav file output code from
// http://www.cplusplus.com/forum/beginner/166954/
// Write the wav file headers
outputFileStream << "RIFF----WAVEfmt "; // (chunk size to be filled in later)
write_word(outputFileStream, 16, 4); // no extension data
write_word(outputFileStream, 1, 2); // PCM - integer samples
write_word(outputFileStream, input->channels, 2); // channels
write_word(outputFileStream, input->sampleRate, 4); // samples per second (Hz)
write_word(outputFileStream, (input->sampleRate * 16 * input->channels) / 8, 4); // (Sample Rate * BitsPerSample * Channels) / 8
write_word(outputFileStream, input->channels * 2, 2); // data block size (size of integer samples, one for each channel, in bytes)
write_word(outputFileStream, 16, 2); // number of bits per sample (use a multiple of 8)
// Write the data chunk header
dataChunkPos = outputFileStream.tellp();
currentFileSize = dataChunkPos;
outputFileStream << "data----"; // (chunk size to be filled in later)
}
void AudioFileWAV::writePayloadToFileStream(const AudioThreadInputPtr& input, size_t startInputPosition, size_t endInputPosition) {
// Prevent clipping
float intScale = (input->peak < 1.0) ? 32767.0f : (32767.0f / input->peak);
if (input->channels == 1) {
for (size_t i = startInputPosition, iMax = endInputPosition; i < iMax; i++) {
write_word(outputFileStream, int(input->data[i] * intScale), 2);
currentFileSize += 2;
}
}
else if (input->channels == 2) {
for (size_t i = startInputPosition, iMax = endInputPosition / 2; i < iMax; i++) {
write_word(outputFileStream, int(input->data[i * 2] * intScale), 2);
write_word(outputFileStream, int(input->data[i * 2 + 1] * intScale), 2);
currentFileSize += 4;
}
}
}
size_t AudioFileWAV::getMaxWritableNumberOfSamples(const AudioThreadInputPtr& input) const {
long long remainingBytesInFile = (long long)(MAX_WAV_FILE_SIZE) - currentFileSize;
return (size_t)(remainingBytesInFile / (input->channels * 2));
}
void AudioFileWAV::setOutputFileName(std::string filename) {
if (filename != filenameBase) {
currentSequenceNumber = 0;
}
AudioFile::setOutputFileName(filename);
}
std::string AudioFileWAV::getOutputFileName() {
std::string recPath = wxGetApp().getConfig()->getRecordingPath();
// Strip any invalid characters from the name
std::string stripChars("<>:\"/\\|?*");
std::string filenameBaseSafe = filenameBase;
for (size_t i = 0, iMax = filenameBaseSafe.length(); i < iMax; i++) {
if (stripChars.find(filenameBaseSafe[i]) != std::string::npos) {
filenameBaseSafe.replace(i, 1, "_");
}
}
// Create output file name
std::stringstream outputFileName;
outputFileName << recPath << filePathSeparator << filenameBaseSafe;
//customized part: append a sequence number.
if (currentSequenceNumber > 0) {
outputFileName << "_" << std::setfill('0') << std::setw(3) << currentSequenceNumber;
}
int idx = 0;
// If the file exists; then find the next non-existing file in sequence.
std::string fileNameCandidate = outputFileName.str();
while (FILE *file = fopen((fileNameCandidate + "." + getExtension()).c_str(), "r")) {
fclose(file);
fileNameCandidate = outputFileName.str() + "-" + std::to_string(++idx);
}
return fileNameCandidate + "." + getExtension();
}

View File

@ -0,0 +1,42 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "AudioFile.h"
#include <fstream>
class AudioFileWAV : public AudioFile {
public:
AudioFileWAV();
~AudioFileWAV() override;
//override to manage name change with multi-part WAV.
void setOutputFileName(std::string filename) override;
//override of the base method to generate multi-part
//WAV to overcome the WAV format size limit.
std::string getOutputFileName() override;
std::string getExtension() override;
bool writeToFile(AudioThreadInputPtr input) override;
bool closeFile() override;
protected:
std::ofstream outputFileStream;
size_t dataChunkPos;
long long currentFileSize = 0;
int currentSequenceNumber = 0;
private:
size_t getMaxWritableNumberOfSamples(const AudioThreadInputPtr& input) const;
void writeHeaderToFileStream(const AudioThreadInputPtr& input);
//write [startInputPosition; endInputPosition[ samples from input into the file.
void writePayloadToFileStream(const AudioThreadInputPtr& input, size_t startInputPosition, size_t endInputPosition);
};

View File

@ -0,0 +1,140 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "AudioSinkFileThread.h"
#include <ctime>
#define HEARTBEAT_CHECK_PERIOD_MICROS (50 * 1000)
AudioSinkFileThread::AudioSinkFileThread() : AudioSinkThread() {
}
AudioSinkFileThread::~AudioSinkFileThread() {
if (audioFileHandler != nullptr) {
audioFileHandler->closeFile();
}
}
void AudioSinkFileThread::sink(AudioThreadInputPtr input) {
if (!audioFileHandler) {
return;
}
//by default, always write something
bool isSomethingToWrite = true;
if (input->is_squelch_active) {
if (squelchOption == SQUELCH_RECORD_SILENCE) {
//patch with "silence"
input->data.assign(input->data.size(), 0.0f);
input->peak = 0.0f;
}
else if (squelchOption == SQUELCH_SKIP_SILENCE) {
isSomethingToWrite = false;
}
}
//else, nothing to do record as if squelch was not enabled.
if (!isSomethingToWrite) {
return;
}
if (fileTimeLimit > 0) {
durationMeasurement.update();
//duration exeeded, close this file and create another
//with "now" as timestamp.
if (durationMeasurement.getSeconds() > fileTimeLimit) {
audioFileHandler->closeFile();
//initialize the filename of the AudioFile with the current time
time_t t = std::time(nullptr);
tm ltm = *std::localtime(&t);
// GCC 5+
// fileName << "_" << std::put_time(&ltm, "%d-%m-%Y_%H-%M-%S");
char timeStr[512];
//International format: Year.Month.Day, also lexicographically sortable
strftime(timeStr, sizeof(timeStr), "%Y-%m-%d_%H-%M-%S", &ltm);
audioFileHandler->setOutputFileName(fileNameBase + std::string("_") + timeStr);
//reset duration counter
durationMeasurement.start();
//the following writeToFile will take care of creating another file.
}
}
// forward to output file handler
audioFileHandler->writeToFile(input);
}
void AudioSinkFileThread::inputChanged(AudioThreadInput /* oldProps */, AudioThreadInputPtr /* newProps */) {
// close, set new parameters, adjust file name sequence and re-open?
if (!audioFileHandler) {
return;
}
audioFileHandler->closeFile();
//reset duration counter
durationMeasurement.start();
}
void AudioSinkFileThread::setAudioFileNameBase(const std::string& baseName) {
fileNameBase = baseName;
}
void AudioSinkFileThread::setAudioFileHandler(AudioFile * output) {
audioFileHandler = output;
//initialize the filename of the AudioFile with the current time
time_t t = std::time(nullptr);
tm ltm = *std::localtime(&t);
// GCC 5+
// fileName << "_" << std::put_time(&ltm, "%d-%m-%Y_%H-%M-%S");
char timeStr[512];
//International format: Year.Month.Day, also lexicographically sortable
strftime(timeStr, sizeof(timeStr), "%Y-%m-%d_%H-%M-%S", &ltm);
audioFileHandler->setOutputFileName(fileNameBase + std::string("_") + timeStr);
// reset Timer
durationMeasurement.start();
}
void AudioSinkFileThread::setSquelchOption(int squelchOptEnumValue) {
if (squelchOptEnumValue == AudioSinkFileThread::SQUELCH_RECORD_SILENCE) {
squelchOption = AudioSinkFileThread::SQUELCH_RECORD_SILENCE;
}
else if (squelchOptEnumValue == AudioSinkFileThread::SQUELCH_SKIP_SILENCE) {
squelchOption = AudioSinkFileThread::SQUELCH_SKIP_SILENCE;
}
else if (squelchOptEnumValue == AudioSinkFileThread::SQUELCH_RECORD_ALWAYS) {
squelchOption = AudioSinkFileThread::SQUELCH_RECORD_ALWAYS;
}
else {
squelchOption = AudioSinkFileThread::SQUELCH_RECORD_SILENCE;
}
}
// Time limit
void AudioSinkFileThread::setFileTimeLimit(int nbSeconds) {
if (nbSeconds > 0) {
fileTimeLimit = nbSeconds;
}
else {
fileTimeLimit = 0;
}
}

View File

@ -0,0 +1,50 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "AudioSinkThread.h"
#include "AudioFile.h"
#include "Timer.h"
class AudioSinkFileThread : public AudioSinkThread {
public:
AudioSinkFileThread();
~AudioSinkFileThread() override;
enum SquelchOption {
SQUELCH_RECORD_SILENCE = 0, // default value, record as a user would hear it.
SQUELCH_SKIP_SILENCE = 1, // skip below-squelch level.
SQUELCH_RECORD_ALWAYS = 2, // record irrespective of the squelch level.
SQUELCH_RECORD_MAX
};
void sink(AudioThreadInputPtr input) override;
void inputChanged(AudioThreadInput oldProps, AudioThreadInputPtr newProps) override;
void setAudioFileHandler(AudioFile *output);
void setAudioFileNameBase(const std::string& baseName);
//Squelch
void setSquelchOption(int squelchOptEnumValue);
// Time limit
void setFileTimeLimit(int nbSeconds);
protected:
std::string fileNameBase;
AudioFile *audioFileHandler = nullptr;
SquelchOption squelchOption = SQUELCH_RECORD_SILENCE;
int fileTimeLimit = 0;
int fileTimeDurationSeconds = -1;
Timer durationMeasurement;
};

View File

@ -0,0 +1,52 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "AudioSinkThread.h"
#define HEARTBEAT_CHECK_PERIOD_MICROS (50 * 1000)
AudioSinkThread::AudioSinkThread() {
inputQueuePtr = std::make_shared<AudioThreadInputQueue>();
inputQueuePtr->set_max_num_items(1000);
setInputQueue("input", inputQueuePtr);
}
AudioSinkThread::~AudioSinkThread() = default;
void AudioSinkThread::run() {
#ifdef __APPLE__
pthread_t tID = pthread_self(); // ID of this thread
int priority = sched_get_priority_max(SCHED_RR) - 1;
sched_param prio = { priority }; // scheduling priority of thread
pthread_setschedparam(tID, SCHED_RR, &prio);
#endif
AudioThreadInputPtr inp;
AudioThreadInput inputRef;
while (!stopping) {
if (!inputQueuePtr->pop(inp, HEARTBEAT_CHECK_PERIOD_MICROS)) {
continue;
}
if (inputRef.channels != inp->channels ||
inputRef.frequency != inp->frequency ||
inputRef.inputRate != inp->inputRate ||
inputRef.sampleRate != inp->sampleRate) {
inputChanged(inputRef, inp);
inputRef.channels = inp->channels;
inputRef.frequency = inp->frequency;
inputRef.inputRate = inp->inputRate;
inputRef.sampleRate = inp->sampleRate;
}
sink(inp);
}
}
void AudioSinkThread::terminate() {
IOThread::terminate();
inputQueuePtr->flush();
}

View File

@ -0,0 +1,25 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "AudioThread.h"
class AudioSinkThread : public IOThread {
public:
AudioSinkThread();
~AudioSinkThread() override;
void run() override;
void terminate() override;
virtual void sink(AudioThreadInputPtr input) = 0;
virtual void inputChanged(AudioThreadInput oldProps, AudioThreadInputPtr newProps) = 0;
protected:
std::recursive_mutex m_mutex;
AudioThreadInputQueuePtr inputQueuePtr;
};

View File

@ -0,0 +1,604 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "AudioThread.h"
#include <vector>
#include <algorithm>
#include "CubicSDR.h"
#include "DemodulatorInstance.h"
#include <mutex>
//50 ms
#define HEARTBEAT_CHECK_PERIOD_MICROS (50 * 1000)
std::map<int, AudioThread* > AudioThread::deviceController;
std::map<int, int> AudioThread::deviceSampleRate;
std::recursive_mutex AudioThread::m_device_mutex;
AudioThread::AudioThread() : IOThread(), nBufferFrames(1024), sampleRate(0), controllerThread(nullptr) {
audioQueuePtr = 0;
underflowCount = 0;
active.store(false);
outputDevice.store(-1);
gain = 1.0;
}
AudioThread::~AudioThread() {
if (controllerThread != nullptr) {
//
//NOT PROTECTED by m_mutex on purpose, to prevent deadlocks with controllerThread
// it doesn't matter, it is only called when all "normal" audio threads are detached from the controller.
//
terminate();
controllerThread->join();
delete controllerThread;
controllerThread = nullptr;
}
}
std::recursive_mutex & AudioThread::getMutex()
{
return m_mutex;
}
void AudioThread::attachControllerThread(std::thread* controllerThread_in) {
controllerThread = controllerThread_in;
}
void AudioThread::bindThread(AudioThread *other) {
std::lock_guard<std::recursive_mutex> lock(m_mutex);
if (std::find(boundThreads.begin(), boundThreads.end(), other) == boundThreads.end()) {
boundThreads.push_back(other);
}
}
void AudioThread::removeThread(AudioThread *other) {
std::lock_guard<std::recursive_mutex> lock(m_mutex);
auto i = std::find(boundThreads.begin(), boundThreads.end(), other);
if (i != boundThreads.end()) {
boundThreads.erase(i);
}
}
void AudioThread::deviceCleanup() {
//
//NOT PROTECTED by m_device_mutex on purpose, to prevent deadlocks with i->second->controllerThread
// it doesn't matter, it is only called when all "normal" audio threads are detached from the controller.
//
for (auto & i : deviceController) {
delete i.second;
}
deviceController.clear();
}
static int audioCallback(void *outputBuffer, void * /* inputBuffer */, unsigned int nBufferFrames, double /* streamTime */, RtAudioStreamStatus status,
void *userData) {
float *out = (float*)outputBuffer;
//Zero output buffer in all cases: this allow to mute audio if no AudioThread data is
//actually active.
::memset(out, 0, nBufferFrames * 2 * sizeof(float));
//src in the controller thread:
auto *src = (AudioThread *)userData;
//by construction, src is a controller thread, from deviceController:
std::lock_guard<std::recursive_mutex> lock(src->getMutex());
if (src->isTerminated()) {
return 1;
}
if (status) {
std::cout << "Audio buffer underflow.." << (src->underflowCount++) << std::endl << std::flush;
}
double peak = 0.0;
//Process the bound threads audio:
for (size_t j = 0; j < src->boundThreads.size(); j++) {
AudioThread *srcmix = src->boundThreads[j];
//lock every single boundThread srcmix in succession the time we process
//its audio samples.
std::lock_guard<std::recursive_mutex> lock(srcmix->getMutex());
if (srcmix->isTerminated() || !srcmix->inputQueue || srcmix->inputQueue->empty() || !srcmix->isActive()) {
continue;
}
if (!srcmix->currentInput) {
srcmix->audioQueuePtr = 0;
if (!srcmix->inputQueue->try_pop(srcmix->currentInput)) {
continue;
}
continue;
}
if (srcmix->currentInput->sampleRate != src->getSampleRate()) {
while (srcmix->inputQueue->try_pop(srcmix->currentInput)) {
if (srcmix->currentInput) {
if (srcmix->currentInput->sampleRate == src->getSampleRate()) {
break;
}
}
srcmix->currentInput = nullptr;
} //end while
srcmix->audioQueuePtr = 0;
if (!srcmix->currentInput) {
continue;
}
}
if (srcmix->currentInput->channels == 0 || srcmix->currentInput->data.empty()) {
if (!srcmix->inputQueue->empty()) {
srcmix->audioQueuePtr = 0;
if (srcmix->currentInput) {
srcmix->currentInput = nullptr;
}
if (!srcmix->inputQueue->try_pop(srcmix->currentInput)) {
continue;
}
}
continue;
}
double mixPeak = srcmix->currentInput->peak * srcmix->gain;
if (srcmix->currentInput->channels == 1) {
for (unsigned int i = 0; i < nBufferFrames; i++) {
if (srcmix->audioQueuePtr >= srcmix->currentInput->data.size()) {
srcmix->audioQueuePtr = 0;
if (srcmix->currentInput) {
srcmix->currentInput = nullptr;
}
if (!srcmix->inputQueue->try_pop(srcmix->currentInput)) {
break;
}
double srcPeak = srcmix->currentInput->peak * srcmix->gain;
if (mixPeak < srcPeak) {
mixPeak = srcPeak;
}
}
if (srcmix->currentInput && !srcmix->currentInput->data.empty()) {
float v = srcmix->currentInput->data[srcmix->audioQueuePtr] * srcmix->gain;
out[i * 2] += v;
out[i * 2 + 1] += v;
}
srcmix->audioQueuePtr++;
}
}
else {
for (unsigned int i = 0, iMax = srcmix->currentInput->channels * nBufferFrames; i < iMax; i++) {
if (srcmix->audioQueuePtr >= srcmix->currentInput->data.size()) {
srcmix->audioQueuePtr = 0;
if (srcmix->currentInput) {
srcmix->currentInput = nullptr;
}
if (!srcmix->inputQueue->try_pop(srcmix->currentInput)) {
break;
}
double srcPeak = srcmix->currentInput->peak * srcmix->gain;
if (mixPeak < srcPeak) {
mixPeak = srcPeak;
}
}
if (srcmix->currentInput && !srcmix->currentInput->data.empty()) {
out[i] = out[i] + srcmix->currentInput->data[srcmix->audioQueuePtr] * srcmix->gain;
}
srcmix->audioQueuePtr++;
}
}
peak += mixPeak;
}
//normalize volume
if (peak > 1.0) {
float invPeak = (float)(1.0 / peak);
for (unsigned int i = 0; i < nBufferFrames * 2; i++) {
out[i] *= invPeak;
}
}
return 0;
}
void AudioThread::enumerateDevices(std::vector<RtAudio::DeviceInfo> &devs) {
RtAudio endac;
unsigned int numDevices = endac.getDeviceCount();
for (unsigned int i = 0; i < numDevices; i++) {
RtAudio::DeviceInfo info = endac.getDeviceInfo(i);
devs.push_back(info);
std::cout << std::endl;
std::cout << "Audio Device #" << i << " " << info.name << std::endl;
std::cout << "\tDefault Output? " << (info.isDefaultOutput ? "Yes" : "No") << std::endl;
std::cout << "\tDefault Input? " << (info.isDefaultInput ? "Yes" : "No") << std::endl;
std::cout << "\tInput channels: " << info.inputChannels << std::endl;
std::cout << "\tOutput channels: " << info.outputChannels << std::endl;
std::cout << "\tDuplex channels: " << info.duplexChannels << std::endl;
std::cout << "\t" << "Native formats:" << std::endl;
RtAudioFormat nFormats = info.nativeFormats;
if (nFormats & RTAUDIO_SINT8) {
std::cout << "\t\t8-bit signed integer." << std::endl;
}
if (nFormats & RTAUDIO_SINT16) {
std::cout << "\t\t16-bit signed integer." << std::endl;
}
if (nFormats & RTAUDIO_SINT24) {
std::cout << "\t\t24-bit signed integer." << std::endl;
}
if (nFormats & RTAUDIO_SINT32) {
std::cout << "\t\t32-bit signed integer." << std::endl;
}
if (nFormats & RTAUDIO_FLOAT32) {
std::cout << "\t\t32-bit float normalized between plus/minus 1.0." << std::endl;
}
if (nFormats & RTAUDIO_FLOAT64) {
std::cout << "\t\t64-bit float normalized between plus/minus 1.0." << std::endl;
}
std::vector<unsigned int>::iterator srate;
std::cout << "\t" << "Supported sample rates:" << std::endl;
for (srate = info.sampleRates.begin(); srate != info.sampleRates.end(); srate++) {
std::cout << "\t\t" << (*srate) << "hz" << std::endl;
}
std::cout << std::endl;
}
}
void AudioThread::setDeviceSampleRate(int deviceId, int sampleRate) {
AudioThread* matchingControllerThread = nullptr;
//scope lock here to minimize the common unique static lock contention
{
std::lock_guard<std::recursive_mutex> lock(m_device_mutex);
if (deviceController.find(deviceId) != deviceController.end()) {
matchingControllerThread = deviceController[deviceId];
}
}
//out-of-lock test
if (matchingControllerThread != nullptr) {
AudioThreadCommand refreshDevice;
refreshDevice.cmdType = AudioThreadCommand::Type::AUDIO_THREAD_CMD_SET_SAMPLE_RATE;
refreshDevice.int_value = sampleRate;
//VSO : blocking push !
matchingControllerThread->getCommandQueue()->push(refreshDevice);
}
}
void AudioThread::setSampleRate(int sampleRate_in) {
bool thisIsAController = false;
//scope lock here to minimize the common unique static lock contention
{
std::lock_guard<std::recursive_mutex> lock(m_device_mutex);
if (deviceController[outputDevice.load()] == this) {
thisIsAController = true;
deviceSampleRate[outputDevice.load()] = sampleRate_in;
}
}
std::lock_guard<std::recursive_mutex> lock(m_mutex);
if (thisIsAController) {
dac.stopStream();
dac.closeStream();
//Set bounded sample rate:
for (auto srcmix : boundThreads) {
srcmix->setSampleRate(sampleRate_in);
}
//make a local copy, snapshot of the list of demodulators
std::vector<DemodulatorInstancePtr> demodulators = wxGetApp().getDemodMgr().getDemodulators();
for (const auto& demod : demodulators) {
if (demod->getOutputDevice() == outputDevice.load()) {
demod->setAudioSampleRate(sampleRate_in);
}
}
dac.openStream(&parameters, nullptr, RTAUDIO_FLOAT32, sampleRate_in, &nBufferFrames, &audioCallback, (void *)this, &opts);
dac.startStream();
}
sampleRate = sampleRate_in;
}
int AudioThread::getSampleRate() {
std::lock_guard<std::recursive_mutex> lock(m_mutex);
return sampleRate;
}
void AudioThread::setupDevice(int deviceId) {
//global lock to setup the device...
std::lock_guard<std::recursive_mutex> lock(m_device_mutex);
parameters.deviceId = deviceId;
parameters.nChannels = 2;
parameters.firstChannel = 0;
opts.streamName = "CubicSDR Audio Output";
try {
if (deviceController.find(outputDevice.load()) != deviceController.end()) {
//'this' is not the controller, so remove it from the bounded list:
//beware, we must take the controller mutex, because the audio callback may use the list of bounded
//threads at that moment:
std::lock_guard<std::recursive_mutex> lock(deviceController[outputDevice.load()]->getMutex());
deviceController[outputDevice.load()]->removeThread(this);
}
#ifndef _MSC_VER
opts.priority = sched_get_priority_max(SCHED_FIFO);
#endif
// opts.flags = RTAUDIO_MINIMIZE_LATENCY;
opts.flags = RTAUDIO_SCHEDULE_REALTIME;
if (deviceSampleRate.find(parameters.deviceId) != deviceSampleRate.end()) {
sampleRate = deviceSampleRate[parameters.deviceId];
}
else {
std::cout << "Error, device sample rate wasn't initialized?" << std::endl;
return;
// sampleRate = AudioThread::getDefaultAudioSampleRate();
// deviceSampleRate[parameters.deviceId] = sampleRate;
}
//Create a new controller:
if (deviceController.find(parameters.deviceId) == deviceController.end()) {
//Create a new controller thread for parameters.deviceId:
auto* newController = new AudioThread();
newController->setInitOutputDevice(parameters.deviceId, sampleRate);
newController->bindThread(this);
newController->attachControllerThread(new std::thread(&AudioThread::threadMain, newController));
deviceController[parameters.deviceId] = newController;
}
else if (deviceController[parameters.deviceId] == this) {
//Attach callback
dac.openStream(&parameters, nullptr, RTAUDIO_FLOAT32, sampleRate, &nBufferFrames, &audioCallback, (void *)this, &opts);
dac.startStream();
}
else {
//we are a bound thread, add ourselves to the controller deviceController[parameters.deviceId].
//beware, we must take the controller mutex, because the audio callback may use the list of bounded
//threads at that moment:
std::lock_guard<std::recursive_mutex> lock(deviceController[parameters.deviceId]->getMutex());
deviceController[parameters.deviceId]->bindThread(this);
}
active = true;
}
catch (RtAudioError& e) {
e.printMessage();
return;
}
if (deviceId != -1) {
outputDevice = deviceId;
}
}
int AudioThread::getOutputDevice() {
std::lock_guard<std::recursive_mutex> lock(m_mutex);
if (outputDevice == -1) {
return dac.getDefaultOutputDevice();
}
return outputDevice;
}
void AudioThread::setInitOutputDevice(int deviceId, int sampleRate_in) {
//global lock
std::lock_guard<std::recursive_mutex> lock(m_device_mutex);
outputDevice = deviceId;
if (sampleRate_in == -1) {
if (deviceSampleRate.find(parameters.deviceId) != deviceSampleRate.end()) {
sampleRate_in = deviceSampleRate[deviceId];
}
}
else {
deviceSampleRate[deviceId] = sampleRate_in;
}
sampleRate = sampleRate_in;
}
void AudioThread::run() {
#ifdef __APPLE__
pthread_t tID = pthread_self(); // ID of this thread
int priority = sched_get_priority_max(SCHED_RR) - 1;
sched_param prio = { priority }; // scheduling priority of thread
pthread_setschedparam(tID, SCHED_RR, &prio);
#endif
// std::cout << "Audio thread initializing.." << std::endl;
if (dac.getDeviceCount() < 1) {
std::cout << "No audio devices found!" << std::endl;
return;
}
setupDevice((outputDevice.load() == -1) ? (dac.getDefaultOutputDevice()) : outputDevice.load());
// std::cout << "Audio thread started." << std::endl;
inputQueue = std::static_pointer_cast<AudioThreadInputQueue>(getInputQueue("AudioDataInput"));
//Infinite loop, witing for commands or for termination
while (!stopping) {
AudioThreadCommand command;
if (!cmdQueue.pop(command, HEARTBEAT_CHECK_PERIOD_MICROS)) {
continue;
}
if (command.cmdType == AudioThreadCommand::Type::AUDIO_THREAD_CMD_SET_DEVICE) {
setupDevice(command.int_value);
}
if (command.cmdType == AudioThreadCommand::Type::AUDIO_THREAD_CMD_SET_SAMPLE_RATE) {
setSampleRate(command.int_value);
}
} //end while
// Drain any remaining inputs, with a non-blocking pop
if (inputQueue != nullptr) {
inputQueue->flush();
}
//Nullify currentInput...
currentInput = nullptr;
//Stop : Retrieve the matching controlling thread in a scope lock:
AudioThread* controllerMatchingThread;
{
std::lock_guard<std::recursive_mutex> global_lock(m_device_mutex);
controllerMatchingThread = deviceController[parameters.deviceId];
}
if (controllerMatchingThread != this) {
//'this' is not the controller, so remove it from the bounded list:
//beware, we must take the controller mutex, because the audio callback may use the list of bounded
//threads at that moment:
std::lock_guard<std::recursive_mutex> lock(controllerMatchingThread->getMutex());
controllerMatchingThread->removeThread(this);
}
else {
// 'this' is a controller thread:
try {
if (dac.isStreamOpen()) {
dac.stopStream();
}
dac.closeStream();
}
catch (RtAudioError& e) {
e.printMessage();
}
}
// std::cout << "Audio thread done." << std::endl;
}
void AudioThread::terminate() {
IOThread::terminate();
}
bool AudioThread::isActive() {
std::lock_guard<std::recursive_mutex> lock(m_mutex);
return active;
}
void AudioThread::setActive(bool state) {
AudioThread* matchingControllerThread = nullptr;
std::lock_guard<std::recursive_mutex> lock(m_mutex);
//scope lock here to minimize the common unique static lock contention
{
std::lock_guard<std::recursive_mutex> lock(m_device_mutex);
if (deviceController.find(parameters.deviceId) != deviceController.end()) {
matchingControllerThread = deviceController[parameters.deviceId];
}
}
if (matchingControllerThread == nullptr) {
return;
}
if (state && !active && inputQueue) {
matchingControllerThread->bindThread(this);
}
else if (!state && active) {
matchingControllerThread->removeThread(this);
}
// Activity state changing, clear any inputs
if (inputQueue) {
inputQueue->flush();
}
active = state;
}
AudioThreadCommandQueue *AudioThread::getCommandQueue() {
return &cmdQueue;
}
void AudioThread::setGain(float gain_in) {
if (gain_in < 0.0) {
gain_in = 0.0;
}
if (gain_in > 2.0) {
gain_in = 2.0;
}
gain = gain_in;
}

View File

@ -0,0 +1,151 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include <queue>
#include <vector>
#include <map>
#include <string>
#include <atomic>
#include <memory>
#include "ThreadBlockingQueue.h"
#include "RtAudio.h"
#include "DemodDefs.h"
class AudioThreadInput {
public:
long long frequency{};
int inputRate{};
int sampleRate{};
int channels{};
float peak{};
int type{};
bool is_squelch_active{};
std::vector<float> data;
AudioThreadInput() :
frequency(0), inputRate(0), sampleRate(0), channels(0), peak(0), type(0), is_squelch_active(false) {
}
explicit AudioThreadInput(AudioThreadInput *copyFrom) {
copy(copyFrom);
}
void copy(AudioThreadInput *copyFrom) {
frequency = copyFrom->frequency;
inputRate = copyFrom->inputRate;
sampleRate = copyFrom->sampleRate;
channels = copyFrom->channels;
peak = copyFrom->peak;
type = copyFrom->type;
is_squelch_active = copyFrom->is_squelch_active;
data.assign(copyFrom->data.begin(), copyFrom->data.end());
}
virtual ~AudioThreadInput() = default;
};
typedef std::shared_ptr<AudioThreadInput> AudioThreadInputPtr;
typedef ThreadBlockingQueue<AudioThreadInputPtr> DemodulatorThreadOutputQueue;
typedef std::shared_ptr<DemodulatorThreadOutputQueue> DemodulatorThreadOutputQueuePtr;
class AudioThreadCommand {
public:
enum class Type {
AUDIO_THREAD_CMD_NULL, AUDIO_THREAD_CMD_SET_DEVICE, AUDIO_THREAD_CMD_SET_SAMPLE_RATE
};
AudioThreadCommand() :
cmdType(AudioThreadCommand::Type::AUDIO_THREAD_CMD_NULL), int_value(0) {
}
AudioThreadCommand::Type cmdType;
int int_value;
};
typedef ThreadBlockingQueue<AudioThreadInputPtr> AudioThreadInputQueue;
typedef ThreadBlockingQueue<AudioThreadCommand> AudioThreadCommandQueue;
typedef std::shared_ptr<AudioThreadInputQueue> AudioThreadInputQueuePtr;
typedef std::shared_ptr<AudioThreadCommandQueue> AudioThreadCommandQueuePtr;
class AudioThread : public IOThread {
public:
AudioThread();
~AudioThread() override;
static void enumerateDevices(std::vector<RtAudio::DeviceInfo> &devs);
void setInitOutputDevice(int deviceId, int sampleRate_in = -1);
int getOutputDevice();
int getSampleRate();
void run() override;
void terminate() override;
bool isActive();
void setActive(bool state);
void setGain(float gain_in);
static std::map<int, int> deviceSampleRate;
AudioThreadCommandQueue *getCommandQueue();
//give access to the this AudioThread lock
std::recursive_mutex& getMutex();
static void deviceCleanup();
static void setDeviceSampleRate(int deviceId, int sampleRate);
//
void attachControllerThread(std::thread* controllerThread);
//fields below, only to be used by other AudioThreads !
size_t underflowCount;
//protected by m_mutex
std::vector<AudioThread *> boundThreads;
AudioThreadInputQueuePtr inputQueue;
AudioThreadInputPtr currentInput;
size_t audioQueuePtr;
float gain;
private:
std::atomic_bool active;
std::atomic_int outputDevice;
RtAudio dac;
unsigned int nBufferFrames;
RtAudio::StreamOptions opts;
RtAudio::StreamParameters parameters;
AudioThreadCommandQueue cmdQueue;
int sampleRate;
//if != nullptr, it mean AudioThread is a controller thread.
std::thread* controllerThread;
//The own m_mutex protecting this AudioThread, in particular boundThreads
std::recursive_mutex m_mutex;
void setupDevice(int deviceId);
void setSampleRate(int sampleRate_in);
void bindThread(AudioThread *other);
void removeThread(AudioThread *other);
static std::map<int, AudioThread* > deviceController;
//The mutex protecting static deviceController, deviceThread and deviceSampleRate access.
static std::recursive_mutex m_device_mutex;
};

View File

@ -0,0 +1,68 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "ThreadBlockingQueue.h"
#include "CubicSDRDefs.h"
#include "liquid/liquid.h"
#include <vector>
#include <atomic>
#include <mutex>
#include <memory>
#include "IOThread.h"
class DemodulatorThread;
class DemodulatorThreadIQData {
public:
long long frequency;
long long sampleRate;
std::vector<liquid_float_complex> data;
DemodulatorThreadIQData() :
frequency(0), sampleRate(0) {
}
DemodulatorThreadIQData & operator=(const DemodulatorThreadIQData &other) {
frequency = other.frequency;
sampleRate = other.sampleRate;
data.assign(other.data.begin(), other.data.end());
return *this;
}
virtual ~DemodulatorThreadIQData() = default;
};
class Modem;
class ModemKit;
class DemodulatorThreadPostIQData {
public:
std::vector<liquid_float_complex> data;
long long sampleRate;
std::string modemName;
std::string modemType;
Modem *modem;
ModemKit *modemKit;
DemodulatorThreadPostIQData() :
sampleRate(0), modem(nullptr), modemKit(nullptr) {
}
virtual ~DemodulatorThreadPostIQData() = default;
};
typedef std::shared_ptr<DemodulatorThreadIQData> DemodulatorThreadIQDataPtr;
typedef std::shared_ptr<DemodulatorThreadPostIQData> DemodulatorThreadPostIQDataPtr;
typedef ThreadBlockingQueue<DemodulatorThreadIQDataPtr> DemodulatorThreadInputQueue;
typedef ThreadBlockingQueue<DemodulatorThreadPostIQDataPtr> DemodulatorThreadPostInputQueue;
typedef std::shared_ptr<DemodulatorThreadInputQueue> DemodulatorThreadInputQueuePtr;
typedef std::shared_ptr<DemodulatorThreadPostInputQueue> DemodulatorThreadPostInputQueuePtr;

View File

@ -0,0 +1,689 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include <memory>
#include <iomanip>
#include "DemodulatorInstance.h"
#include "CubicSDR.h"
#include "DemodulatorThread.h"
#include "DemodulatorPreThread.h"
#include "AudioSinkFileThread.h"
#include "AudioFileWAV.h"
#if USE_HAMLIB
#include "RigThread.h"
#endif
DemodVisualCue::DemodVisualCue() {
squelchBreak.store(false);
}
DemodVisualCue::~DemodVisualCue() = default;
void DemodVisualCue::triggerSquelchBreak(int counter) {
squelchBreak.store(counter);
}
int DemodVisualCue::getSquelchBreak() {
return squelchBreak.load();
}
void DemodVisualCue::step() {
if (squelchBreak.load()) {
squelchBreak--;
if (squelchBreak.load() < 0) {
squelchBreak.store(0);
}
}
}
DemodulatorInstance::DemodulatorInstance() {
#if ENABLE_DIGITAL_LAB
activeOutput = nullptr;
#endif
active.store(false);
muted.store(false);
recording.store(false);
deltaLock.store(false);
deltaLockOfs.store(0);
currentOutputDevice.store(-1);
currentAudioGain.store(1.0);
follow.store(false);
tracking.store(false);
label.store(new std::string("Unnamed"));
user_label.store(new std::wstring());
pipeIQInputData = std::make_shared<DemodulatorThreadInputQueue>();
pipeIQInputData->set_max_num_items(100);
pipeIQDemodData = std::make_shared< DemodulatorThreadPostInputQueue>();
pipeIQDemodData->set_max_num_items(100);
audioThread = new AudioThread();
demodulatorPreThread = new DemodulatorPreThread(this);
demodulatorPreThread->setInputQueue("IQDataInput",pipeIQInputData);
demodulatorPreThread->setOutputQueue("IQDataOutput",pipeIQDemodData);
pipeAudioData = std::make_shared<AudioThreadInputQueue>();
pipeAudioData->set_max_num_items(100);
demodulatorThread = new DemodulatorThread(this);
demodulatorThread->setInputQueue("IQDataInput",pipeIQDemodData);
demodulatorThread->setOutputQueue("AudioDataOutput", pipeAudioData);
audioThread->setInputQueue("AudioDataInput", pipeAudioData);
}
DemodulatorInstance::~DemodulatorInstance() {
std::lock_guard < std::recursive_mutex > lockData(m_thread_control_mutex);
//now that DemodulatorInstance are managed through shared_ptr, we
//should enter here ONLY when it is no longer used by any piece of code, anywhere.
//so active wait on IsTerminated(), then die.
#define TERMINATION_SPIN_WAIT_MS (20)
#define MAX_WAIT_FOR_TERMINATION_MS (3000.0)
//this is a stupid busy plus sleep loop
int nbCyclesToWait = (MAX_WAIT_FOR_TERMINATION_MS / TERMINATION_SPIN_WAIT_MS) + 1;
int currentCycle = 0;
while (currentCycle < nbCyclesToWait) {
if (isTerminated()) {
std::cout << "Garbage collected demodulator instance '" << getLabel() << "'... " << std::endl << std::flush;
#if ENABLE_DIGITAL_LAB
delete activeOutput;
#endif
delete demodulatorPreThread;
delete demodulatorThread;
delete audioThread;
delete audioSinkThread;
break;
}
else {
std::this_thread::sleep_for(std::chrono::milliseconds(TERMINATION_SPIN_WAIT_MS));
}
currentCycle++;
} //end while
}
void DemodulatorInstance::setVisualOutputQueue(const DemodulatorThreadOutputQueuePtr& tQueue) {
demodulatorThread->setOutputQueue("AudioVisualOutput", tQueue);
}
void DemodulatorInstance::run() {
std::lock_guard < std::recursive_mutex > lockData(m_thread_control_mutex);
if (active) {
return;
}
t_Audio = new std::thread(&AudioThread::threadMain, audioThread);
#ifdef __APPLE__ // Already using pthreads, might as well do some custom init..
pthread_attr_t attr;
size_t size;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, 2048000);
pthread_attr_getstacksize(&attr, &size);
pthread_create(&t_PreDemod, &attr, &DemodulatorPreThread::pthread_helper, demodulatorPreThread);
pthread_attr_destroy(&attr);
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, 2048000);
pthread_attr_getstacksize(&attr, &size);
pthread_create(&t_Demod, &attr, &DemodulatorThread::pthread_helper, demodulatorThread);
pthread_attr_destroy(&attr);
// std::cout << "Initialized demodulator stack size of " << size << std::endl;
#else
t_PreDemod = new std::thread(&DemodulatorPreThread::threadMain, demodulatorPreThread);
t_Demod = new std::thread(&DemodulatorThread::threadMain, demodulatorThread);
#endif
active = true;
}
void DemodulatorInstance::updateLabel(long long freq) {
std::stringstream newLabel;
newLabel.precision(3);
newLabel << std::fixed << ((long double) freq / 1000000.0);
setLabel(newLabel.str());
wxGetApp().getBookmarkMgr().updateActiveList();
}
void DemodulatorInstance::terminate() {
#if ENABLE_DIGITAL_LAB
if (activeOutput) {
closeOutput();
}
#endif
// std::cout << "Terminating demodulator audio thread.." << std::endl;
audioThread->terminate();
// std::cout << "Terminating demodulator thread.." << std::endl;
demodulatorThread->terminate();
// std::cout << "Terminating demodulator preprocessor thread.." << std::endl;
demodulatorPreThread->terminate();
if (audioSinkThread != nullptr) {
stopRecording();
}
//that will actually unblock the currently blocked push().
pipeIQInputData->flush();
pipeAudioData->flush();
pipeIQDemodData->flush();
}
std::string DemodulatorInstance::getLabel() {
return *(label.load());
}
void DemodulatorInstance::setLabel(std::string labelStr) {
delete label.exchange(new std::string(labelStr));
}
bool DemodulatorInstance::isTerminated() {
std::lock_guard < std::recursive_mutex > lockData(m_thread_control_mutex);
bool audioTerminated = audioThread->isTerminated();
bool demodTerminated = demodulatorThread->isTerminated();
bool preDemodTerminated = demodulatorPreThread->isTerminated();
bool audioSinkTerminated = (audioSinkThread == nullptr) || audioSinkThread->isTerminated();
//Cleanup the worker threads, if the threads are indeed terminated.
// threads are linked as t_PreDemod ==> t_Demod ==> t_Audio
//so terminate in the same order to starve the following threads in succession.
//i.e waiting on timed-pop so able to se their stopping flag.
if (preDemodTerminated) {
if (t_PreDemod) {
#ifdef __APPLE__
pthread_join(t_PreDemod, NULL);
#else
t_PreDemod->join();
delete t_PreDemod;
#endif
t_PreDemod = nullptr;
}
}
if (demodTerminated) {
if (t_Demod) {
#ifdef __APPLE__
pthread_join(t_Demod, nullptr);
#else
t_Demod->join();
delete t_Demod;
#endif
t_Demod = nullptr;
}
}
if (audioTerminated) {
if (t_Audio) {
#ifdef __APPLE__
pthread_join(t_PreDemod, NULL);
#else
t_Audio->join();
delete t_Audio;
#endif
t_Audio = nullptr;
}
}
if (audioSinkTerminated) {
if (t_AudioSink != nullptr) {
t_AudioSink->join();
delete t_AudioSink;
t_AudioSink = nullptr;
}
}
bool terminated = audioTerminated && demodTerminated && preDemodTerminated && audioSinkTerminated;
return terminated;
}
bool DemodulatorInstance::isActive() {
return active;
}
void DemodulatorInstance::setActive(bool state) {
if (active && !state) {
#if ENABLE_DIGITAL_LAB
if (activeOutput) {
activeOutput->Hide();
}
#endif
audioThread->setActive(state);
DemodulatorThread::releaseSquelchLock(this);
} else if (!active && state) {
#if ENABLE_DIGITAL_LAB
if (activeOutput && getModemType() == "digital") {
activeOutput->Show();
}
#endif
audioThread->setActive(state);
}
if (!state) {
tracking = false;
}
active = state;
wxGetApp().getBookmarkMgr().updateActiveList();
}
void DemodulatorInstance::squelchAuto() {
demodulatorThread->setSquelchEnabled(true);
}
bool DemodulatorInstance::isSquelchEnabled() {
return demodulatorThread->isSquelchEnabled();
}
void DemodulatorInstance::setSquelchEnabled(bool state) {
demodulatorThread->setSquelchEnabled(state);
}
float DemodulatorInstance::getSignalLevel() {
return demodulatorThread->getSignalLevel();
}
float DemodulatorInstance::getSignalFloor() {
return demodulatorThread->getSignalFloor();
}
float DemodulatorInstance::getSignalCeil() {
return demodulatorThread->getSignalCeil();
}
void DemodulatorInstance::setSquelchLevel(float signal_level_in) {
demodulatorThread->setSquelchLevel(signal_level_in);
wxGetApp().getDemodMgr().setLastSquelchLevel(signal_level_in);
wxGetApp().getDemodMgr().setLastSquelchEnabled(true);
}
float DemodulatorInstance::getSquelchLevel() {
return demodulatorThread->getSquelchLevel();
}
void DemodulatorInstance::setOutputDevice(int device_id) {
if (!active) {
audioThread->setInitOutputDevice(device_id);
} else if (audioThread) {
AudioThreadCommand command;
command.cmdType = AudioThreadCommand::Type::AUDIO_THREAD_CMD_SET_DEVICE;
command.int_value = device_id;
//VSO: blocking push
audioThread->getCommandQueue()->push(command);
}
setAudioSampleRate(AudioThread::deviceSampleRate[device_id]);
currentOutputDevice = device_id;
}
int DemodulatorInstance::getOutputDevice() {
if (currentOutputDevice == -1) {
if (audioThread) {
currentOutputDevice = audioThread->getOutputDevice();
}
}
return currentOutputDevice;
}
void DemodulatorInstance::setDemodulatorType(const std::string& demod_type_in) {
setGain(getGain());
if (demodulatorPreThread) {
std::string currentDemodType = demodulatorPreThread->getDemodType();
if ((!currentDemodType.empty()) && (currentDemodType != demod_type_in)) {
lastModemSettings[currentDemodType] = demodulatorPreThread->readModemSettings();
lastModemBandwidth[currentDemodType] = demodulatorPreThread->getBandwidth();
}
#if ENABLE_DIGITAL_LAB
if (activeOutput) {
activeOutput->Hide();
}
#endif
demodulatorPreThread->setDemodType(demod_type_in);
int lastbw = 0;
if (!currentDemodType.empty() && lastModemBandwidth.find(demod_type_in) != lastModemBandwidth.end()) {
lastbw = lastModemBandwidth[demod_type_in];
}
if (!lastbw) {
lastbw = Modem::getModemDefaultSampleRate(demod_type_in);
}
if (lastbw) {
setBandwidth(lastbw);
}
#if ENABLE_DIGITAL_LAB
if (isModemInitialized() && getModemType() == "digital") {
auto *outp = (ModemDigitalOutputConsole *)getOutput();
outp->setTitle(getDemodulatorType() + ": " + frequencyToStr(getFrequency()));
}
#endif
}
wxGetApp().getBookmarkMgr().updateActiveList();
}
std::string DemodulatorInstance::getDemodulatorType() {
return demodulatorPreThread->getDemodType();
}
std::wstring DemodulatorInstance::getDemodulatorUserLabel() {
return *(user_label.load());
}
void DemodulatorInstance::setDemodulatorUserLabel(const std::wstring& demod_user_label) {
delete user_label.exchange(new std::wstring(demod_user_label));
}
void DemodulatorInstance::setDemodulatorLock(bool demod_lock_in) {
Modem *cModem = demodulatorPreThread->getModem();
if (cModem && cModem->getType() == "digital") {
((ModemDigital *)cModem)->setDemodulatorLock(demod_lock_in);
}
}
int DemodulatorInstance::getDemodulatorLock() {
Modem *cModem = demodulatorPreThread->getModem();
if (cModem && cModem->getType() == "digital") {
return ((ModemDigital *)cModem)->getDemodulatorLock();
}
return 0;
}
void DemodulatorInstance::setBandwidth(int bw) {
demodulatorPreThread->setBandwidth(bw);
}
int DemodulatorInstance::getBandwidth() {
return demodulatorPreThread->getBandwidth();
}
void DemodulatorInstance::setFrequency(long long freq) {
if ((freq - getBandwidth() / 2) <= 0) {
freq = getBandwidth() / 2;
}
demodulatorPreThread->setFrequency(freq);
#if ENABLE_DIGITAL_LAB
if (activeOutput) {
if (isModemInitialized() && getModemType() == "digital") {
auto *outp = (ModemDigitalOutputConsole *)getOutput();
outp->setTitle(getDemodulatorType() + ": " + frequencyToStr(getFrequency()));
}
}
#endif
#if USE_HAMLIB
if (wxGetApp().rigIsActive() && wxGetApp().getRigThread()->getFollowModem() &&
wxGetApp().getDemodMgr().getCurrentModem().get() == this) {
wxGetApp().getRigThread()->setFrequency(freq,true);
}
#endif
if (this->isActive()) {
wxGetApp().getBookmarkMgr().updateActiveList();
}
}
long long DemodulatorInstance::getFrequency() {
return demodulatorPreThread->getFrequency();
}
void DemodulatorInstance::setAudioSampleRate(int sampleRate) {
demodulatorPreThread->setAudioSampleRate(sampleRate);
}
int DemodulatorInstance::getAudioSampleRate() const {
if (!audioThread) {
return 0;
}
return audioThread->getSampleRate();
}
void DemodulatorInstance::setGain(float gain_in) {
currentAudioGain = gain_in;
audioThread->setGain(gain_in);
}
float DemodulatorInstance::getGain() {
return currentAudioGain;
}
bool DemodulatorInstance::isFollow() {
return follow.load();
}
void DemodulatorInstance::setFollow(bool follow_in) {
follow.store(follow_in);
}
bool DemodulatorInstance::isTracking() {
return tracking.load();
}
void DemodulatorInstance::setTracking(bool tracking_in) {
tracking.store(tracking_in);
}
bool DemodulatorInstance::isDeltaLock() {
return deltaLock.load();
}
void DemodulatorInstance::setDeltaLock(bool lock) {
deltaLock.store(lock);
}
void DemodulatorInstance::setDeltaLockOfs(int lockOfs) {
deltaLockOfs.store(lockOfs);
}
int DemodulatorInstance::getDeltaLockOfs() {
return deltaLockOfs.load();
}
bool DemodulatorInstance::isMuted() {
return demodulatorThread->isMuted();
}
void DemodulatorInstance::setMuted(bool muted_in) {
muted = muted_in;
demodulatorThread->setMuted(muted_in);
wxGetApp().getDemodMgr().setLastMuted(muted_in);
}
bool DemodulatorInstance::isRecording()
{
return recording.load();
}
void DemodulatorInstance::setRecording(bool recording_in)
{
if (recording_in) {
startRecording();
}
else {
stopRecording();
}
}
DemodVisualCue *DemodulatorInstance::getVisualCue() {
return &visualCue;
}
DemodulatorThreadInputQueuePtr DemodulatorInstance::getIQInputDataPipe() {
return pipeIQInputData;
}
ModemArgInfoList DemodulatorInstance::getModemArgs() {
Modem *m = demodulatorPreThread->getModem();
ModemArgInfoList args;
if (m != nullptr) {
args = m->getSettings();
}
return args;
}
std::string DemodulatorInstance::readModemSetting(const std::string& setting) {
return demodulatorPreThread->readModemSetting(setting);
}
void DemodulatorInstance::writeModemSetting(const std::string& setting, std::string value) {
demodulatorPreThread->writeModemSetting(setting, value);
}
ModemSettings DemodulatorInstance::readModemSettings() {
return demodulatorPreThread->readModemSettings();
}
void DemodulatorInstance::writeModemSettings(ModemSettings settings) {
demodulatorPreThread->writeModemSettings(settings);
}
bool DemodulatorInstance::isModemInitialized() {
if (!demodulatorPreThread || isTerminated()) {
return false;
}
return demodulatorPreThread->isInitialized();
}
std::string DemodulatorInstance::getModemType() {
if (isModemInitialized()) {
return demodulatorPreThread->getModem()->getType();
}
return "";
}
ModemSettings DemodulatorInstance::getLastModemSettings(const std::string& demodType) {
if (lastModemSettings.find(demodType) != lastModemSettings.end()) {
return lastModemSettings[demodType];
} else {
ModemSettings mods;
return mods;
}
}
void DemodulatorInstance::startRecording() {
if (recording.load()) {
return;
}
auto *newSinkThread = new AudioSinkFileThread();
auto *afHandler = new AudioFileWAV();
std::stringstream fileName;
std::wstring userLabel = getDemodulatorUserLabel();
wxString userLabelForFileName(userLabel);
std::string userLabelStr = userLabelForFileName.ToStdString();
if (!userLabelStr.empty()) {
fileName << userLabelStr;
} else {
fileName << getLabel();
}
newSinkThread->setAudioFileNameBase(fileName.str());
//attach options:
newSinkThread->setSquelchOption(wxGetApp().getConfig()->getRecordingSquelchOption());
newSinkThread->setFileTimeLimit(wxGetApp().getConfig()->getRecordingFileTimeLimit());
newSinkThread->setAudioFileHandler(afHandler);
audioSinkThread = newSinkThread;
t_AudioSink = new std::thread(&AudioSinkThread::threadMain, audioSinkThread);
demodulatorThread->setOutputQueue("AudioSink", audioSinkThread->getInputQueue("input"));
recording.store(true);
}
void DemodulatorInstance::stopRecording() {
if (!recording.load()) {
return;
}
demodulatorThread->setOutputQueue("AudioSink", nullptr);
audioSinkThread->terminate();
t_AudioSink->join();
delete t_AudioSink;
delete audioSinkThread;
t_AudioSink = nullptr;
audioSinkThread = nullptr;
recording.store(false);
}
#if ENABLE_DIGITAL_LAB
ModemDigitalOutput *DemodulatorInstance::getOutput() {
if (activeOutput == nullptr) {
activeOutput = new ModemDigitalOutputConsole();
}
return activeOutput;
}
void DemodulatorInstance::showOutput() {
if (activeOutput != nullptr) {
activeOutput->Show();
}
}
void DemodulatorInstance::hideOutput() {
if (activeOutput != nullptr) {
activeOutput->Hide();
}
}
void DemodulatorInstance::closeOutput() {
if (isModemInitialized()) {
if (getModemType() == "digital") {
auto *dModem = (ModemDigital *)demodulatorPreThread->getModem();
dModem->setOutput(nullptr);
}
}
if (activeOutput) {
activeOutput->Close();
}
}
#endif

View File

@ -0,0 +1,178 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include <vector>
#include <map>
#include <thread>
#include <memory>
#include "DemodDefs.h"
#include "ModemDigital.h"
#include "ModemAnalog.h"
#include "AudioThread.h"
#include "AudioSinkThread.h"
#if ENABLE_DIGITAL_LAB
#include "DigitalConsole.h"
#endif
class DemodVisualCue {
public:
DemodVisualCue();
~DemodVisualCue();
void triggerSquelchBreak(int counter);
int getSquelchBreak();
void step();
private:
std::atomic_int squelchBreak;
};
class DemodulatorThread;
class DemodulatorPreThread;
class DemodulatorInstance {
public:
#ifdef __APPLE__
pthread_t t_PreDemod;
pthread_t t_Demod;
#else
std::thread *t_PreDemod = nullptr;
std::thread *t_Demod = nullptr;
#endif
AudioThread *audioThread = nullptr;
std::thread *t_Audio = nullptr;
DemodulatorInstance();
~DemodulatorInstance();
void setVisualOutputQueue(const DemodulatorThreadOutputQueuePtr& tQueue);
void run();
void terminate();
std::string getLabel();
void setLabel(std::string labelStr);
bool isTerminated();
void updateLabel(long long freq);
bool isActive();
void setActive(bool state);
void squelchAuto();
bool isSquelchEnabled();
void setSquelchEnabled(bool state);
float getSignalLevel();
float getSignalFloor();
float getSignalCeil();
void setSquelchLevel(float signal_level_in);
float getSquelchLevel();
void setOutputDevice(int device_id);
int getOutputDevice();
void setDemodulatorType(const std::string& demod_type_in);
std::string getDemodulatorType();
std::wstring getDemodulatorUserLabel();
void setDemodulatorUserLabel(const std::wstring& demod_user_label);
void setDemodulatorLock(bool demod_lock_in);
int getDemodulatorLock();
void setBandwidth(int bw);
int getBandwidth();
void setGain(float gain_in);
float getGain();
void setFrequency(long long freq);
long long getFrequency();
void setAudioSampleRate(int sampleRate);
int getAudioSampleRate() const;
bool isFollow();
void setFollow(bool follow_in);
bool isTracking();
void setTracking(bool tracking_in);
bool isDeltaLock();
void setDeltaLock(bool lock);
void setDeltaLockOfs(int lockOfs);
int getDeltaLockOfs();
bool isMuted();
void setMuted(bool muted_in);
bool isRecording();
void setRecording(bool recording);
DemodVisualCue *getVisualCue();
DemodulatorThreadInputQueuePtr getIQInputDataPipe();
ModemArgInfoList getModemArgs();
std::string readModemSetting(const std::string& setting);
ModemSettings readModemSettings();
void writeModemSetting(const std::string& setting, std::string value);
void writeModemSettings(ModemSettings settings);
bool isModemInitialized();
std::string getModemType();
ModemSettings getLastModemSettings(const std::string& demodType);
#if ENABLE_DIGITAL_LAB
ModemDigitalOutput *getOutput();
void showOutput();
void hideOutput();
void closeOutput();
#endif
protected:
void startRecording();
void stopRecording();
private:
DemodulatorThreadInputQueuePtr pipeIQInputData;
DemodulatorThreadPostInputQueuePtr pipeIQDemodData;
AudioThreadInputQueuePtr pipeAudioData;
DemodulatorPreThread *demodulatorPreThread;
DemodulatorThread *demodulatorThread;
AudioSinkThread *audioSinkThread = nullptr;
std::thread *t_AudioSink = nullptr;
AudioThreadInputQueuePtr audioSinkInputQueue;
//protects child thread creation and termination
std::recursive_mutex m_thread_control_mutex;
std::atomic<std::string *> label; //
// User editable buffer, 16 bit string.
std::atomic<std::wstring *> user_label;
std::atomic_bool active;
std::atomic_bool muted;
std::atomic_bool deltaLock;
std::atomic_bool recording;
std::atomic_int deltaLockOfs;
std::atomic_int currentOutputDevice;
std::atomic<float> currentAudioGain;
std::atomic_bool follow, tracking;
std::map<std::string, ModemSettings> lastModemSettings;
std::map<std::string, int> lastModemBandwidth;
DemodVisualCue visualCue;
#if ENABLE_DIGITAL_LAB
ModemDigitalOutput *activeOutput;
#endif
};
typedef std::shared_ptr<DemodulatorInstance> DemodulatorInstancePtr;

View File

@ -0,0 +1,588 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include <DemodulatorMgr.h>
#include <algorithm>
#include <string>
#include "CubicSDR.h"
#if USE_HAMLIB
#include "RigThread.h"
#endif
#include "DataTree.h"
#include <wx/string.h>
bool demodFreqCompare (const DemodulatorInstancePtr& i, const DemodulatorInstancePtr& j) { return (i->getFrequency() < j->getFrequency()); }
bool inactiveCompare (const DemodulatorInstancePtr& i, const DemodulatorInstancePtr& j) { return (i->isActive() < j->isActive()); }
DemodulatorMgr::DemodulatorMgr() {
lastBandwidth = DEFAULT_DEMOD_BW;
lastDemodType = DEFAULT_DEMOD_TYPE;
lastSquelchEnabled = false;
lastSquelch = -100;
lastGain = 1.0;
lastMuted = false;
lastDeltaLock = false;
}
DemodulatorMgr::~DemodulatorMgr() {
terminateAll();
}
DemodulatorInstancePtr DemodulatorMgr::newThread() {
std::lock_guard < std::recursive_mutex > lock(demods_busy);
//create a new instance of DemodulatorInstance here.
DemodulatorInstancePtr newDemod = std::make_shared<DemodulatorInstance>();
std::stringstream label;
label << demods.size();
newDemod->setLabel(label.str());
demods.push_back(newDemod);
return newDemod;
}
void DemodulatorMgr::terminateAll() {
std::lock_guard < std::recursive_mutex > lock(demods_busy);
while (!demods.empty()) {
DemodulatorInstancePtr d = demods.back();
demods.pop_back();
deleteThread(d);
}
}
std::vector<DemodulatorInstancePtr> DemodulatorMgr::getDemodulators() {
std::lock_guard < std::recursive_mutex > lock(demods_busy);
return demods;
}
std::vector<DemodulatorInstancePtr> DemodulatorMgr::getOrderedDemodulators(bool actives) {
std::lock_guard < std::recursive_mutex > lock(demods_busy);
auto demods_ordered = demods;
if (actives) {
std::sort(demods_ordered.begin(), demods_ordered.end(), inactiveCompare);
std::vector<DemodulatorInstancePtr>::iterator i;
for (i = demods_ordered.begin(); i != demods_ordered.end(); i++) {
if ((*i)->isActive()) {
break;
}
}
if (i == demods_ordered.end()) {
demods_ordered.erase(demods_ordered.begin(), demods_ordered.end());
} else if ((*i) != demods_ordered.front()) {
demods_ordered.erase(demods_ordered.begin(), i);
}
}
//if by chance they have the same frequency, keep their relative order
std::stable_sort(demods_ordered.begin(), demods_ordered.end(), demodFreqCompare);
return demods_ordered;
}
DemodulatorInstancePtr DemodulatorMgr::getPreviousDemodulator(const DemodulatorInstancePtr& demod, bool actives) {
std::lock_guard < std::recursive_mutex > lock(demods_busy);
if (!getCurrentModem()) {
return nullptr;
}
auto demods_ordered = getOrderedDemodulators(actives);
auto p = std::find(demods_ordered.begin(), demods_ordered.end(), demod);
if (p == demods_ordered.end()) {
return nullptr;
}
if (*p == demods_ordered.front()) {
return demods_ordered.back();
}
return *(--p);
}
DemodulatorInstancePtr DemodulatorMgr::getNextDemodulator(const DemodulatorInstancePtr& demod, bool actives) {
std::lock_guard < std::recursive_mutex > lock(demods_busy);
if (!getCurrentModem()) {
return nullptr;
}
auto demods_ordered = getOrderedDemodulators(actives);
auto p = std::find(demods_ordered.begin(), demods_ordered.end(), demod);
if (actives) {
}
if (p == demods_ordered.end()) {
return nullptr;
}
if (*p == demods_ordered.back()) {
return demods_ordered.front();
}
return *(++p);
}
DemodulatorInstancePtr DemodulatorMgr::getLastDemodulator() {
std::lock_guard < std::recursive_mutex > lock(demods_busy);
return getOrderedDemodulators().back();
}
DemodulatorInstancePtr DemodulatorMgr::getFirstDemodulator() {
std::lock_guard < std::recursive_mutex > lock(demods_busy);
return getOrderedDemodulators().front();
}
void DemodulatorMgr::deleteThread(const DemodulatorInstancePtr& demod) {
std::lock_guard < std::recursive_mutex > lock(demods_busy);
wxGetApp().getBookmarkMgr().addRecent(demod);
auto i = std::find(demods.begin(), demods.end(), demod);
if (activeContextModem == demod) {
activeContextModem = nullptr;
}
if (currentModem == demod) {
currentModem = nullptr;
}
if (activeVisualDemodulator == demod) {
activeVisualDemodulator = nullptr;
}
if (i != demods.end()) {
demods.erase(i);
}
//Ask for termination
demod->setActive(false);
demod->terminate();
}
std::vector<DemodulatorInstancePtr> DemodulatorMgr::getDemodulatorsAt(long long freq, int bandwidth) {
std::lock_guard < std::recursive_mutex > lock(demods_busy);
std::vector<DemodulatorInstancePtr> foundDemods;
for (auto testDemod : demods) {
long long freqTest = testDemod->getFrequency();
long long bandwidthTest = testDemod->getBandwidth();
long long halfBandwidthTest = bandwidthTest / 2;
long long halfBuffer = bandwidth / 2;
if ((freq <= (freqTest + ((testDemod->getDemodulatorType() != "LSB")?halfBandwidthTest:0) + halfBuffer)) && (freq >= (freqTest - ((testDemod->getDemodulatorType() != "USB")?halfBandwidthTest:0) - halfBuffer))) {
foundDemods.push_back(testDemod);
}
}
return foundDemods;
}
bool DemodulatorMgr::anyDemodulatorsAt(long long freq, int bandwidth) {
std::lock_guard < std::recursive_mutex > lock(demods_busy);
for (auto testDemod : demods) {
long long freqTest = testDemod->getFrequency();
long long bandwidthTest = testDemod->getBandwidth();
long long halfBandwidthTest = bandwidthTest / 2;
long long halfBuffer = bandwidth / 2;
if ((freq <= (freqTest + ((testDemod->getDemodulatorType() != "LSB")?halfBandwidthTest:0) + halfBuffer)) && (freq >= (freqTest - ((testDemod->getDemodulatorType() != "USB")?halfBandwidthTest:0) - halfBuffer))) {
return true;
}
}
return false;
}
void DemodulatorMgr::setActiveDemodulator(const DemodulatorInstancePtr& demod, bool temporary) {
std::lock_guard < std::recursive_mutex > lock(demods_busy);
// Should this be made the current modem (i.e. clicked, toggled)
if (!temporary) {
if (activeContextModem != nullptr) {
currentModem = activeContextModem;
updateLastState();
} else {
currentModem = demod;
}
updateLastState();
wxGetApp().getBookmarkMgr().updateActiveList();
#if USE_HAMLIB
if (wxGetApp().rigIsActive() && wxGetApp().getRigThread()->getFollowModem() && currentModem) {
wxGetApp().getRigThread()->setFrequency(currentModem->getFrequency(),true);
}
#endif
}
// TODO: This is probably unnecessary and confusing
if (activeVisualDemodulator) {
activeVisualDemodulator->setVisualOutputQueue(nullptr);
}
if (demod) {
demod->setVisualOutputQueue(wxGetApp().getAudioVisualQueue());
activeVisualDemodulator = demod;
} else {
DemodulatorInstancePtr last = getCurrentModem();
if (last) {
last->setVisualOutputQueue(wxGetApp().getAudioVisualQueue());
}
activeVisualDemodulator = last;
}
// :ODOT
activeContextModem = demod;
}
//Dangerous: this is only intended by some internal classes
void DemodulatorMgr::setActiveDemodulatorByRawPointer(DemodulatorInstance* demod, bool temporary) {
std::lock_guard < std::recursive_mutex > lock(demods_busy);
for (const auto& existing_demod : demods) {
if (existing_demod.get() == demod) {
setActiveDemodulator(existing_demod, temporary);
break;
}
}
}
/**
* Get the currently focused modem, i.e. the one hovered by interaction
* If no active context modem is available the current modem is returned
* @return Active Context Modem
*/
DemodulatorInstancePtr DemodulatorMgr::getActiveContextModem() {
std::lock_guard < std::recursive_mutex > lock(demods_busy);
if (activeContextModem && !activeContextModem->isActive()) {
activeContextModem = getCurrentModem();
}
return activeContextModem;
}
/**
* Get the last selected / focused modem
* This is the currently active modem
* @return Current Modem
*/
DemodulatorInstancePtr DemodulatorMgr::getCurrentModem() {
return currentModem;
}
DemodulatorInstancePtr DemodulatorMgr::getLastDemodulatorWith(const std::string& type,
const std::wstring& userLabel,
long long frequency,
int bandwidth) {
std::lock_guard < std::recursive_mutex > lock(demods_busy);
//backwards search:
for (auto it = demods.rbegin(); it != demods.rend(); it++) {
if ((*it)->getDemodulatorType() == type &&
(*it)->getDemodulatorUserLabel() == userLabel &&
(*it)->getFrequency() == frequency &&
(*it)->getBandwidth() == bandwidth) {
return (*it);
}
}
return nullptr;
}
void DemodulatorMgr::updateLastState() {
std::lock_guard < std::recursive_mutex > lock(demods_busy);
if (std::find(demods.begin(), demods.end(), currentModem) == demods.end()) {
if (activeContextModem && activeContextModem->isActive()) {
currentModem = activeContextModem;
} else if (activeContextModem && !activeContextModem->isActive()){
activeContextModem = nullptr;
currentModem = nullptr;
}
}
if (currentModem && !currentModem->isActive()) {
currentModem = nullptr;
}
if (currentModem) {
lastBandwidth = currentModem->getBandwidth();
lastDemodType = currentModem->getDemodulatorType();
lastDemodLock = currentModem->getDemodulatorLock() != 0;
lastSquelchEnabled = currentModem->isSquelchEnabled();
lastSquelch = currentModem->getSquelchLevel();
lastGain = currentModem->getGain();
lastModemSettings[lastDemodType] = currentModem->readModemSettings();
}
}
int DemodulatorMgr::getLastBandwidth() const {
return lastBandwidth;
}
void DemodulatorMgr::setLastBandwidth(int lastBandwidth_in) {
if (lastBandwidth_in < MIN_BANDWIDTH) {
lastBandwidth_in = MIN_BANDWIDTH;
} else if (lastBandwidth_in > wxGetApp().getSampleRate()) {
lastBandwidth_in = wxGetApp().getSampleRate();
}
lastBandwidth = lastBandwidth_in;
}
std::string DemodulatorMgr::getLastDemodulatorType() const {
return lastDemodType;
}
void DemodulatorMgr::setLastDemodulatorType(std::string lastDemodType_in) {
lastDemodType = lastDemodType_in;
}
float DemodulatorMgr::getLastGain() const {
return lastGain;
}
void DemodulatorMgr::setLastGain(float lastGain_in) {
lastGain = lastGain_in;
}
bool DemodulatorMgr::getLastDeltaLock() const {
return lastDeltaLock;
}
void DemodulatorMgr::setLastDeltaLock(bool lock) {
lastDeltaLock = lock;
}
float DemodulatorMgr::getLastSquelchLevel() const {
return lastSquelch;
}
void DemodulatorMgr::setLastSquelchLevel(float lastSquelch_in) {
lastSquelch = lastSquelch_in;
}
bool DemodulatorMgr::isLastSquelchEnabled() const {
return lastSquelchEnabled;
}
void DemodulatorMgr::setLastSquelchEnabled(bool lastSquelchEnabled_in) {
lastSquelchEnabled = lastSquelchEnabled_in;
}
bool DemodulatorMgr::isLastMuted() const {
return lastMuted;
}
void DemodulatorMgr::setLastMuted(bool lastMuted_in) {
lastMuted = lastMuted_in;
}
ModemSettings DemodulatorMgr::getLastModemSettings(const std::string& modemType) {
return lastModemSettings[modemType];
}
void DemodulatorMgr::setLastModemSettings(const std::string& modemType, ModemSettings settings) {
lastModemSettings[modemType] = settings;
}
void DemodulatorMgr::setOutputDevices(std::map<int,RtAudio::DeviceInfo> devs) {
outputDevices = devs;
}
std::map<int, RtAudio::DeviceInfo> DemodulatorMgr::getOutputDevices() {
return outputDevices;
}
void DemodulatorMgr::saveInstance(DataNode *node, const DemodulatorInstancePtr& inst) {
*node->newChild("bandwidth") = inst->getBandwidth();
*node->newChild("frequency") = inst->getFrequency();
*node->newChild("type") = inst->getDemodulatorType();
node->newChild("user_label")->element()->set(inst->getDemodulatorUserLabel());
*node->newChild("squelch_level") = inst->getSquelchLevel();
*node->newChild("squelch_enabled") = inst->isSquelchEnabled() ? 1 : 0;
*node->newChild("output_device") = outputDevices[inst->getOutputDevice()].name;
*node->newChild("gain") = inst->getGain();
*node->newChild("muted") = inst->isMuted() ? 1 : 0;
if (inst->isDeltaLock()) {
*node->newChild("delta_lock") = inst->isDeltaLock() ? 1 : 0;
*node->newChild("delta_ofs") = inst->getDeltaLockOfs();
}
if (inst == getCurrentModem()) {
*node->newChild("active") = 1;
}
ModemSettings saveSettings = inst->readModemSettings();
if (!saveSettings.empty()) {
DataNode *settingsNode = node->newChild("settings");
for (ModemSettings::const_iterator msi = saveSettings.begin(); msi != saveSettings.end(); msi++) {
*settingsNode->newChild(msi->first.c_str()) = msi->second;
}
}
}
std::wstring DemodulatorMgr::getSafeWstringValue(DataNode* node) {
std::wstring decodedWString;
if (node != nullptr) {
//1) decode as encoded wstring:
try {
node->element()->get(decodedWString);
} catch (const DataTypeMismatchException &) {
//2) wstring decode fail, try simple std::string
std::string decodedStdString;
try {
node->element()->get(decodedStdString);
//use wxString for a clean conversion to a wstring:
decodedWString = wxString(decodedStdString).ToStdWstring();
} catch (const DataTypeMismatchException &) {
//nothing works, return an empty string.
decodedWString = L"";
}
}
}
return decodedWString;
}
DemodulatorInstancePtr DemodulatorMgr::loadInstance(DataNode *node) {
std::lock_guard < std::recursive_mutex > lock(demods_busy);
DemodulatorInstancePtr newDemod = nullptr;
node->rewindAll();
long bandwidth = (long)*node->getNext("bandwidth");
long long freq = (long long)*node->getNext("frequency");
float squelch_level = node->hasAnother("squelch_level") ? (float) *node->getNext("squelch_level") : 0;
int squelch_enabled = node->hasAnother("squelch_enabled") ? (int) *node->getNext("squelch_enabled") : 0;
int muted = node->hasAnother("muted") ? (int) *node->getNext("muted") : 0;
int delta_locked = node->hasAnother("delta_lock") ? (int) *node->getNext("delta_lock") : 0;
int delta_ofs = node->hasAnother("delta_ofs") ? (int) *node->getNext("delta_ofs") : 0;
std::string output_device = node->hasAnother("output_device") ? ((string)*(node->getNext("output_device"))) : "";
float gain = node->hasAnother("gain") ? (float) *node->getNext("gain") : 1.0f;
std::string type = "FM";
DataNode *demodTypeNode = node->hasAnother("type")?node->getNext("type"):nullptr;
if (demodTypeNode && demodTypeNode->element()->getDataType() == DataElement::Type::DATA_INT) {
int legacyType = (int)*demodTypeNode;
int legacyStereo = node->hasAnother("stereo") ? (int) *node->getNext("stereo") : 0;
switch (legacyType) { // legacy demod ID
case 1: type = legacyStereo?"FMS":"FM"; break;
case 2: type = "AM"; break;
case 3: type = "LSB"; break;
case 4: type = "USB"; break;
case 5: type = "DSB"; break;
case 6: type = "ASK"; break;
case 7: type = "APSK"; break;
case 8: type = "BPSK"; break;
case 9: type = "DPSK"; break;
case 10: type = "PSK"; break;
case 11: type = "OOK"; break;
case 12: type = "ST"; break;
case 13: type = "SQAM"; break;
case 14: type = "QAM"; break;
case 15: type = "QPSK"; break;
case 16: type = "I/Q"; break;
default: type = "FM"; break;
}
} else if (demodTypeNode && demodTypeNode->element()->getDataType() == DataElement::Type::DATA_STRING) {
demodTypeNode->element()->get(type);
}
//read the user label associated with the demodulator
std::wstring user_label;
DataNode *demodUserLabel = node->hasAnother("user_label") ? node->getNext("user_label") : nullptr;
if (demodUserLabel) {
user_label = DemodulatorMgr::getSafeWstringValue(demodUserLabel);
}
ModemSettings mSettings;
if (node->hasAnother("settings")) {
DataNode *modemSettings = node->getNext("settings");
for (int msi = 0, numSettings = modemSettings->numChildren(); msi < numSettings; msi++) {
DataNode *settingNode = modemSettings->child(msi);
std::string keyName = settingNode->getName();
std::string strSettingValue = settingNode->element()->toString();
if (!keyName.empty() && !strSettingValue.empty()) {
mSettings[keyName] = strSettingValue;
}
}
}
newDemod = newThread();
newDemod->setDemodulatorType(type);
newDemod->setDemodulatorUserLabel(user_label);
newDemod->writeModemSettings(mSettings);
newDemod->setBandwidth(bandwidth);
newDemod->setFrequency(freq);
newDemod->setGain(gain);
newDemod->updateLabel(freq);
newDemod->setMuted(muted != 0);
if (delta_locked) {
newDemod->setDeltaLock(true);
newDemod->setDeltaLockOfs(delta_ofs);
}
if (squelch_enabled) {
newDemod->setSquelchEnabled(true);
newDemod->setSquelchLevel(squelch_level);
}
//Attach to sound output:
std::map<int, RtAudio::DeviceInfo>::iterator i;
bool matching_device_found = false;
for (i = outputDevices.begin(); i != outputDevices.end(); i++) {
if (i->second.name == output_device) {
newDemod->setOutputDevice(i->first);
matching_device_found = true;
break;
}
}
//if no device is found, choose the first of the list anyway.
if (!matching_device_found) {
newDemod->setOutputDevice(outputDevices.begin()->first);
}
return newDemod;
}

View File

@ -0,0 +1,109 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include <vector>
#include <map>
#include <thread>
#include "DemodulatorInstance.h"
class DataNode;
class DemodulatorMgr {
public:
DemodulatorMgr();
~DemodulatorMgr();
DemodulatorInstancePtr newThread();
//return snapshot-copy of the list purposefully
std::vector<DemodulatorInstancePtr> getDemodulators();
std::vector<DemodulatorInstancePtr> getOrderedDemodulators(bool actives = true);
std::vector<DemodulatorInstancePtr> getDemodulatorsAt(long long freq, int bandwidth);
DemodulatorInstancePtr getPreviousDemodulator(const DemodulatorInstancePtr& demod, bool actives = true);
DemodulatorInstancePtr getNextDemodulator(const DemodulatorInstancePtr& demod, bool actives = true);
DemodulatorInstancePtr getLastDemodulator();
DemodulatorInstancePtr getFirstDemodulator();
bool anyDemodulatorsAt(long long freq, int bandwidth);
void deleteThread(const DemodulatorInstancePtr&);
void terminateAll();
void setActiveDemodulator(const DemodulatorInstancePtr& demod, bool temporary = true);
//Dangerous: this is only intended by some internal classes,
// and only set a pre-existing demod
void setActiveDemodulatorByRawPointer(DemodulatorInstance* demod, bool temporary = true);
DemodulatorInstancePtr getActiveContextModem();
DemodulatorInstancePtr getCurrentModem();
DemodulatorInstancePtr getLastDemodulatorWith(const std::string& type,
const std::wstring& userLabel,
long long frequency,
int bandwidth);
int getLastBandwidth() const;
void setLastBandwidth(int lastBandwidth_in);
std::string getLastDemodulatorType() const;
void setLastDemodulatorType(std::string lastDemodType_in);
float getLastGain() const;
void setLastGain(float lastGain_in);
bool getLastDeltaLock() const;
void setLastDeltaLock(bool lock);
float getLastSquelchLevel() const;
void setLastSquelchLevel(float lastSquelch_in);
bool isLastSquelchEnabled() const;
void setLastSquelchEnabled(bool lastSquelchEnabled_in);
bool isLastMuted() const;
void setLastMuted(bool lastMuted_in);
ModemSettings getLastModemSettings(const std::string&);
void setLastModemSettings(const std::string&, ModemSettings);
void updateLastState();
void setOutputDevices(std::map<int,RtAudio::DeviceInfo> devs);
std::map<int, RtAudio::DeviceInfo> getOutputDevices();
void saveInstance(DataNode *node, const DemodulatorInstancePtr& inst);
DemodulatorInstancePtr loadInstance(DataNode *node);
private:
//utility method that attempts to decode node value as std::wstring, else as std::string, else
//return an empty string.
static std::wstring getSafeWstringValue(DataNode* node);
std::vector<DemodulatorInstancePtr> demods;
DemodulatorInstancePtr activeContextModem;
DemodulatorInstancePtr currentModem;
DemodulatorInstancePtr activeVisualDemodulator;
int lastBandwidth;
std::string lastDemodType;
bool lastDemodLock;
bool lastSquelchEnabled;
float lastSquelch;
float lastGain;
bool lastMuted;
bool lastDeltaLock;
//protects access to demods lists and such, need to be recursive
//because of the usage of public re-entrant methods
std::recursive_mutex demods_busy;
std::map<std::string, ModemSettings> lastModemSettings;
std::map<int,RtAudio::DeviceInfo> outputDevices;
};

View File

@ -0,0 +1,402 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include <vector>
#ifdef __APPLE__
#include <pthread.h>
#endif
#include "DemodulatorPreThread.h"
#include "CubicSDR.h"
#include "DemodulatorInstance.h"
//50 ms
#define HEARTBEAT_CHECK_PERIOD_MICROS (50 * 1000)
DemodulatorPreThread::DemodulatorPreThread(DemodulatorInstance* parent) : IOThread(), iqResampler(nullptr), iqResampleRatio(1), cModem(nullptr), cModemKit(nullptr)
{
initialized.store(false);
this->parent = parent;
freqShifter = nco_crcf_create(LIQUID_VCO);
shiftFrequency = 0;
workerQueue = std::make_shared<DemodulatorThreadWorkerCommandQueue>();
workerQueue->set_max_num_items(2);
workerResults = std::make_shared<DemodulatorThreadWorkerResultQueue>();
workerResults->set_max_num_items(100);
workerThread = new DemodulatorWorkerThread();
workerThread->setInputQueue("WorkerCommandQueue",workerQueue);
workerThread->setOutputQueue("WorkerResultQueue",workerResults);
newSampleRate = currentSampleRate = 0;
newBandwidth = currentBandwidth = 0;
newAudioSampleRate = currentAudioSampleRate = 0;
newFrequency = currentFrequency = 0;
sampleRateChanged.store(false);
frequencyChanged.store(false);
bandwidthChanged.store(false);
audioSampleRateChanged.store(false);
modemSettingsChanged.store(false);
demodTypeChanged.store(false);
}
bool DemodulatorPreThread::isInitialized() {
return initialized.load();
}
DemodulatorPreThread::~DemodulatorPreThread() = default;
void DemodulatorPreThread::run() {
#ifdef __APPLE__
pthread_t tID = pthread_self(); // ID of this thread
int priority = sched_get_priority_max( SCHED_FIFO) - 1;
sched_param prio = {priority}; // scheduling priority of thread
pthread_setschedparam(tID, SCHED_FIFO, &prio);
#endif
// std::cout << "Demodulator preprocessor thread started.." << std::endl;
ReBuffer<DemodulatorThreadPostIQData> buffers("DemodulatorPreThreadBuffers");
iqInputQueue = std::static_pointer_cast<DemodulatorThreadInputQueue>(getInputQueue("IQDataInput"));
iqOutputQueue = std::static_pointer_cast<DemodulatorThreadPostInputQueue>(getOutputQueue("IQDataOutput"));
std::vector<liquid_float_complex> in_buf_data;
std::vector<liquid_float_complex> out_buf_data;
t_Worker = new std::thread(&DemodulatorWorkerThread::threadMain, workerThread);
while (!stopping) {
DemodulatorThreadIQDataPtr inp;
if (!iqInputQueue->pop(inp, HEARTBEAT_CHECK_PERIOD_MICROS)) {
continue;
}
if (frequencyChanged.load()) {
currentFrequency.store(newFrequency);
frequencyChanged.store(false);
}
if (inp->sampleRate != currentSampleRate) {
newSampleRate = inp->sampleRate;
if (newSampleRate) {
sampleRateChanged.store(true);
}
}
if (!newAudioSampleRate) {
newAudioSampleRate = parent->getAudioSampleRate();
if (newAudioSampleRate) {
audioSampleRateChanged.store(true);
}
} else if (parent->getAudioSampleRate() != newAudioSampleRate) {
if (parent->getAudioSampleRate()) {
newAudioSampleRate = parent->getAudioSampleRate();
audioSampleRateChanged.store(true);
}
}
if (demodTypeChanged.load() && (newSampleRate && newAudioSampleRate && newBandwidth)) {
DemodulatorWorkerThreadCommand command(DemodulatorWorkerThreadCommand::Type::DEMOD_WORKER_THREAD_CMD_MAKE_DEMOD);
command.frequency = newFrequency;
command.sampleRate = newSampleRate;
command.demodType = newDemodType;
command.bandwidth = newBandwidth;
command.audioSampleRate = newAudioSampleRate;
demodType = newDemodType;
sampleRateChanged.store(false);
audioSampleRateChanged.store(false);
ModemSettings lastSettings = parent->getLastModemSettings(newDemodType);
if (!lastSettings.empty()) {
command.settings = lastSettings;
if (!modemSettingsBuffered.empty()) {
for (ModemSettings::const_iterator msi = modemSettingsBuffered.begin(); msi != modemSettingsBuffered.end(); msi++) {
command.settings[msi->first] = msi->second;
}
}
} else {
command.settings = modemSettingsBuffered;
}
modemSettingsBuffered.clear();
modemSettingsChanged.store(false);
//VSO: blocking push
workerQueue->push(command);
cModem = nullptr;
cModemKit = nullptr;
demodTypeChanged.store(false);
initialized.store(false);
}
else if (
cModemKit && cModem &&
(bandwidthChanged.load() || sampleRateChanged.load() || audioSampleRateChanged.load() || cModem->shouldRebuildKit()) &&
(newSampleRate && newAudioSampleRate && newBandwidth)
) {
DemodulatorWorkerThreadCommand command(DemodulatorWorkerThreadCommand::Type::DEMOD_WORKER_THREAD_CMD_BUILD_FILTERS);
command.frequency = newFrequency;
command.sampleRate = newSampleRate;
command.bandwidth = newBandwidth;
command.audioSampleRate = newAudioSampleRate;
bandwidthChanged.store(false);
sampleRateChanged.store(false);
audioSampleRateChanged.store(false);
modemSettingsBuffered.clear();
//VSO: blocking
workerQueue->push(command);
}
// Requested frequency is not center, shift it into the center!
if ((currentFrequency - inp->frequency) != shiftFrequency) {
shiftFrequency = currentFrequency - inp->frequency;
if (abs(shiftFrequency) <= (int) ((double) (inp->sampleRate / 2) * 1.5)) {
nco_crcf_set_frequency(freqShifter, (2.0 * M_PI) * (((double) abs(shiftFrequency)) / ((double) inp->sampleRate)));
}
}
if (cModem && cModemKit && abs(shiftFrequency) > (int) ((double) (inp->sampleRate / 2) * 1.5)) {
continue;
}
// std::lock_guard < std::mutex > lock(inp->m_mutex);
std::vector<liquid_float_complex> *data = &inp->data;
if (!data->empty() && (inp->sampleRate == currentSampleRate) && cModem && cModemKit) {
size_t bufSize = data->size();
if (in_buf_data.size() != bufSize) {
if (in_buf_data.capacity() < bufSize) {
in_buf_data.reserve(bufSize);
out_buf_data.reserve(bufSize);
}
in_buf_data.resize(bufSize);
out_buf_data.resize(bufSize);
}
in_buf_data.assign(inp->data.begin(), inp->data.end());
liquid_float_complex *in_buf = &in_buf_data[0];
liquid_float_complex *out_buf = &out_buf_data[0];
liquid_float_complex *temp_buf;
if (shiftFrequency != 0) {
if (shiftFrequency < 0) {
nco_crcf_mix_block_up(freqShifter, in_buf, out_buf, bufSize);
} else {
nco_crcf_mix_block_down(freqShifter, in_buf, out_buf, bufSize);
}
temp_buf = in_buf;
in_buf = out_buf;
out_buf = temp_buf;
}
DemodulatorThreadPostIQDataPtr resamp = buffers.getBuffer();
size_t out_size = ceil((double) (bufSize) * iqResampleRatio) + 512;
if (resampledData.size() != out_size) {
if (resampledData.capacity() < out_size) {
resampledData.reserve(out_size);
}
resampledData.resize(out_size);
}
unsigned int numWritten;
msresamp_crcf_execute(iqResampler, in_buf, bufSize, &resampledData[0], &numWritten);
resamp->data.assign(resampledData.begin(), resampledData.begin() + numWritten);
resamp->modemType = cModem->getType();
resamp->modemName = cModem->getName();
resamp->modem = cModem;
resamp->modemKit = cModemKit;
resamp->sampleRate = currentBandwidth;
//VSO: blocking push
iqOutputQueue->push(resamp);
}
DemodulatorWorkerThreadResult result;
//process all worker results until
while (!stopping && workerResults->try_pop(result)) {
switch (result.cmd) {
case DemodulatorWorkerThreadResult::Type::DEMOD_WORKER_THREAD_RESULT_FILTERS:
if (result.iqResampler) {
if (iqResampler) {
msresamp_crcf_destroy(iqResampler);
}
iqResampler = result.iqResampler;
iqResampleRatio = result.iqResampleRatio;
}
if (result.modem != nullptr) {
cModem = result.modem;
#if ENABLE_DIGITAL_LAB
if (cModem->getType() == "digital") {
auto *mDigi = (ModemDigital *)cModem;
mDigi->setOutput(parent->getOutput());
}
#endif
}
if (result.modemKit != nullptr) {
cModemKit = result.modemKit;
currentAudioSampleRate = cModemKit->audioSampleRate;
}
if (result.bandwidth) {
currentBandwidth = result.bandwidth;
}
if (result.sampleRate) {
currentSampleRate = result.sampleRate;
}
if (!result.modemName.empty()) {
demodType = result.modemName;
demodTypeChanged.store(false);
}
shiftFrequency = inp->frequency-1;
initialized.store(cModem != nullptr);
break;
default:
break;
}
} //end while
if ((cModem != nullptr) && modemSettingsChanged.load()) {
cModem->writeSettings(modemSettingsBuffered);
modemSettingsBuffered.clear();
modemSettingsChanged.store(false);
}
} //end while stopping
iqOutputQueue->flush();
iqInputQueue->flush();
}
void DemodulatorPreThread::setDemodType(std::string demodType_in) {
newDemodType = demodType_in;
demodTypeChanged.store(true);
}
std::string DemodulatorPreThread::getDemodType() {
if (demodTypeChanged.load()) {
return newDemodType;
}
return demodType;
}
void DemodulatorPreThread::setFrequency(long long freq) {
frequencyChanged.store(true);
newFrequency = freq;
}
long long DemodulatorPreThread::getFrequency() {
if (frequencyChanged.load()) {
return newFrequency;
}
return currentFrequency;
}
void DemodulatorPreThread::setSampleRate(long long sampleRate) {
sampleRateChanged.store(true);
newSampleRate = sampleRate;
}
long long DemodulatorPreThread::getSampleRate() {
if (sampleRateChanged.load()) {
return newSampleRate;
}
return currentSampleRate;
}
void DemodulatorPreThread::setBandwidth(int bandwidth) {
bandwidthChanged.store(true);
newBandwidth = bandwidth;
}
int DemodulatorPreThread::getBandwidth() {
// if (bandwidthChanged.load()) {
// return newBandwidth;
// }
return currentBandwidth;
}
void DemodulatorPreThread::setAudioSampleRate(int rate) {
audioSampleRateChanged.store(true);
newAudioSampleRate = rate;
}
int DemodulatorPreThread::getAudioSampleRate() {
if (audioSampleRateChanged.load()) {
return newAudioSampleRate;
}
return currentAudioSampleRate;
}
void DemodulatorPreThread::terminate() {
//make non-blocking calls to be sure threads are flagged for termination.
IOThread::terminate();
workerThread->terminate();
//unblock the push()
iqOutputQueue->flush();
iqInputQueue->flush();
//wait blocking for termination here, it could be long with lots of modems and we MUST terminate properly,
//else better kill the whole application...
workerThread->isTerminated(5000);
t_Worker->join();
delete t_Worker;
t_Worker = nullptr;
delete workerThread;
workerThread = nullptr;
}
Modem *DemodulatorPreThread::getModem() {
return cModem;
}
ModemKit *DemodulatorPreThread::getModemKit() {
return cModemKit;
}
std::string DemodulatorPreThread::readModemSetting(const std::string& setting) {
if (cModem) {
return cModem->readSetting(setting);
} else if (modemSettingsBuffered.find(setting) != modemSettingsBuffered.end()) {
return modemSettingsBuffered[setting];
}
return "";
}
void DemodulatorPreThread::writeModemSetting(const std::string& setting, std::string value) {
modemSettingsBuffered[setting] = value;
modemSettingsChanged.store(true);
}
ModemSettings DemodulatorPreThread::readModemSettings() {
if (cModem) {
return cModem->readSettings();
} else {
return modemSettingsBuffered;
}
}
void DemodulatorPreThread::writeModemSettings(ModemSettings settings) {
modemSettingsBuffered = settings;
modemSettingsChanged.store(true);
}

View File

@ -0,0 +1,89 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include <queue>
#include <vector>
#include <atomic>
#include <memory>
#include "CubicSDRDefs.h"
#include "DemodDefs.h"
#include "DemodulatorWorkerThread.h"
class DemodulatorInstance;
class DemodulatorPreThread : public IOThread {
public:
explicit DemodulatorPreThread(DemodulatorInstance* parent);
~DemodulatorPreThread() override;
void run() override;
void setDemodType(std::string demodType_in);
std::string getDemodType();
void setFrequency(long long sampleRate);
long long getFrequency();
void setSampleRate(long long sampleRate);
long long getSampleRate();
void setBandwidth(int bandwidth);
int getBandwidth();
void setAudioSampleRate(int rate);
int getAudioSampleRate();
bool isInitialized();
void terminate() override;
Modem *getModem();
ModemKit *getModemKit();
std::string readModemSetting(const std::string& setting);
void writeModemSetting(const std::string& setting, std::string value);
ModemSettings readModemSettings();
void writeModemSettings(ModemSettings settings);
protected:
DemodulatorInstance* parent;
msresamp_crcf iqResampler;
double iqResampleRatio;
std::vector<liquid_float_complex> resampledData;
Modem *cModem;
ModemKit *cModemKit;
std::atomic_llong currentSampleRate, newSampleRate;
std::atomic_llong currentFrequency, newFrequency;
std::atomic_int currentBandwidth, newBandwidth;
std::atomic_int currentAudioSampleRate, newAudioSampleRate;
std::atomic_bool sampleRateChanged, frequencyChanged, bandwidthChanged, audioSampleRateChanged;
ModemSettings modemSettingsBuffered;
std::atomic_bool modemSettingsChanged;
nco_crcf freqShifter;
int shiftFrequency;
std::atomic_bool initialized;
std::atomic_bool demodTypeChanged;
std::string demodType;
std::string newDemodType;
DemodulatorWorkerThread *workerThread;
std::thread *t_Worker;
DemodulatorThreadWorkerCommandQueuePtr workerQueue;
DemodulatorThreadWorkerResultQueuePtr workerResults;
DemodulatorThreadInputQueuePtr iqInputQueue;
DemodulatorThreadPostInputQueuePtr iqOutputQueue;
};

View File

@ -0,0 +1,414 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "DemodulatorThread.h"
#include "DemodulatorInstance.h"
#include "CubicSDR.h"
#include <vector>
#include <cmath>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
//50 ms
#define HEARTBEAT_CHECK_PERIOD_MICROS (50 * 1000)
#ifdef __APPLE__
#include <pthread.h>
#endif
DemodulatorInstance* DemodulatorThread::squelchLock(nullptr);
std::mutex DemodulatorThread::squelchLockMutex;
DemodulatorThread::DemodulatorThread(DemodulatorInstance* parent)
: IOThread(), demodInstance(parent), outputBuffers("DemodulatorThreadBuffers"), muted(false), squelchLevel(-100),
signalLevel(-100), signalFloor(-30), signalCeil(30), squelchEnabled(false), squelchBreak(false) {
}
DemodulatorThread::~DemodulatorThread() {
releaseSquelchLock(demodInstance);
}
void DemodulatorThread::onBindOutput(std::string name, ThreadQueueBasePtr threadQueue) {
if (name == "AudioVisualOutput") {
//protects because it may be changed at runtime
std::lock_guard < SpinMutex > lock(m_mutexAudioVisOutputQueue);
audioVisOutputQueue = std::static_pointer_cast<DemodulatorThreadOutputQueue>(threadQueue);
}
if (name == "AudioSink") {
std::lock_guard < SpinMutex > lock(m_mutexAudioVisOutputQueue);
audioSinkOutputQueue = std::static_pointer_cast<AudioThreadInputQueue>(threadQueue);
}
}
double DemodulatorThread::abMagnitude(float inphase, float quadrature) {
// cast to double, so we keep precision despite the **2 op later.
double dinphase = (double)inphase;
double dquadrature = (double)quadrature;
//sqrt() has been an insanely fast intrinsic for years, use it !
return sqrt(dinphase * dinphase + dquadrature * dquadrature);
}
double DemodulatorThread::linearToDb(double linear) {
#define SMALL 1e-20
if (linear <= SMALL) {
linear = double(SMALL);
}
return 20.0 * log10(linear);
}
void DemodulatorThread::run() {
#ifdef __APPLE__
pthread_t tID = pthread_self(); // ID of this thread
int priority = sched_get_priority_max( SCHED_FIFO )-1;
sched_param prio = {priority}; // scheduling priority of thread
pthread_setschedparam(tID, SCHED_FIFO, &prio);
#endif
// std::cout << "Demodulator thread started.." << std::endl;
iqInputQueue = std::static_pointer_cast<DemodulatorThreadPostInputQueue>(getInputQueue("IQDataInput"));
audioOutputQueue = std::static_pointer_cast<AudioThreadInputQueue>(getOutputQueue("AudioDataOutput"));
ModemIQData modemData;
while (!stopping) {
DemodulatorThreadPostIQDataPtr inp;
if (!iqInputQueue->pop(inp, HEARTBEAT_CHECK_PERIOD_MICROS)) {
continue;
}
size_t bufSize = inp->data.size();
if (!bufSize) {
continue;
}
if (inp->modemKit && inp->modemKit != cModemKit) {
if (cModemKit != nullptr) {
cModem->disposeKit(cModemKit);
}
cModemKit = inp->modemKit;
}
if (inp->modem && inp->modem != cModem) {
delete cModem;
cModem = inp->modem;
}
if (!cModem || !cModemKit) {
continue;
}
std::vector<liquid_float_complex> *inputData;
inputData = &inp->data;
modemData.sampleRate = inp->sampleRate;
modemData.data.assign(inputData->begin(), inputData->end());
AudioThreadInputPtr ati = nullptr;
ModemAnalog *modemAnalog = (cModem->getType() == "analog")?((ModemAnalog *)cModem):nullptr;
ModemDigital *modemDigital = (cModem->getType() == "digital")?((ModemDigital *)cModem):nullptr;
if (modemAnalog != nullptr) {
ati = outputBuffers.getBuffer();
ati->sampleRate = cModemKit->audioSampleRate;
ati->inputRate = inp->sampleRate;
} else if (modemDigital != nullptr) {
ati = outputBuffers.getBuffer();
ati->sampleRate = cModemKit->sampleRate;
ati->inputRate = inp->sampleRate;
ati->data.resize(0);
}
cModem->demodulate(cModemKit, &modemData, ati.get());
double currentSignalLevel = 0;
double sampleTime = double(inp->data.size()) / double(inp->sampleRate);
if (audioOutputQueue != nullptr && ati && !ati->data.empty()) {
double accum = 0;
if (cModem->useSignalOutput()) {
for (auto i : ati->data) {
accum += abMagnitude(i, 0.0);
}
currentSignalLevel = linearToDb(accum / double(ati->data.size()));
} else {
for (auto i : inp->data) {
accum += abMagnitude(i.real, i.imag);
}
currentSignalLevel = linearToDb(accum / double(inp->data.size()));
}
float sf = signalFloor, sc = signalCeil, sl = squelchLevel;
if (currentSignalLevel > sc) {
sc = currentSignalLevel;
}
if (currentSignalLevel < sf) {
sf = currentSignalLevel;
}
if (sl+1.0f > sc) {
sc = sl+1.0f;
}
if ((sf+2.0f) > sc) {
sc = sf+2.0f;
}
sc -= (sc - (currentSignalLevel + 2.0f)) * sampleTime * 0.05f;
sf += ((currentSignalLevel - 5.0f) - sf) * sampleTime * 0.15f;
signalFloor = sf;
signalCeil = sc;
}
if (currentSignalLevel > signalLevel) {
signalLevel = signalLevel + (currentSignalLevel - signalLevel) * 0.5;
} else {
signalLevel = signalLevel + (currentSignalLevel - signalLevel) * 0.05 * sampleTime * 30.0;
}
bool squelched = squelchEnabled && (signalLevel < squelchLevel);
if (squelchEnabled) {
if (!squelched && !squelchBreak) {
if (wxGetApp().getSoloMode() && !wxGetApp().getAppFrame()->isUserDemodBusy()) {
std::lock_guard < std::mutex > lock(squelchLockMutex);
if (squelchLock == nullptr) {
squelchLock = demodInstance;
wxGetApp().getDemodMgr().setActiveDemodulator(nullptr);
wxGetApp().getDemodMgr().setActiveDemodulatorByRawPointer(demodInstance, false);
squelchBreak = true;
demodInstance->getVisualCue()->triggerSquelchBreak(120);
}
} else {
squelchBreak = true;
demodInstance->getVisualCue()->triggerSquelchBreak(120);
}
} else if (squelched && squelchBreak) {
releaseSquelchLock(demodInstance);
squelchBreak = false;
}
}
//compute audio peak:
if (audioOutputQueue != nullptr && ati) {
ati->peak = 0;
for (auto data_i : ati->data) {
float p = fabs(data_i);
if (p > ati->peak) {
ati->peak = p;
}
}
}
//attach squelch flag to samples, to be used by audio sink.
if (ati) {
ati->is_squelch_active = squelched;
}
//At that point, capture the current state of audioVisOutputQueue in a local
//variable, and works with it with now on until the next while-turn.
DemodulatorThreadOutputQueuePtr localAudioVisOutputQueue = nullptr;
{
std::lock_guard < SpinMutex > lock(m_mutexAudioVisOutputQueue);
localAudioVisOutputQueue = audioVisOutputQueue;
}
if (!squelched && (ati || modemDigital) && localAudioVisOutputQueue != nullptr && localAudioVisOutputQueue->empty()) {
AudioThreadInputPtr ati_vis = std::make_shared<AudioThreadInput>();
ati_vis->sampleRate = inp->sampleRate;
ati_vis->inputRate = inp->sampleRate;
size_t num_vis = DEMOD_VIS_SIZE;
if (modemDigital) {
if (ati) { // TODO: handle digital modems with audio output
ati = nullptr;
}
ati_vis->data.resize(inputData->size());
ati_vis->channels = 2;
for (int i = 0, iMax = inputData->size() / 2; i < iMax; i++) {
ati_vis->data[i * 2] = (*inputData)[i].real;
ati_vis->data[i * 2 + 1] = (*inputData)[i].imag;
}
ati_vis->type = 2;
} else if (ati->channels==2) {
ati_vis->channels = 2;
int stereoSize = ati->data.size();
if (stereoSize > DEMOD_VIS_SIZE * 2) {
stereoSize = DEMOD_VIS_SIZE * 2;
}
ati_vis->data.resize(stereoSize);
if (inp->modemName == "I/Q") {
for (int i = 0; i < stereoSize / 2; i++) {
ati_vis->data[i] = (*inputData)[i].real * 0.75;
ati_vis->data[i + stereoSize / 2] = (*inputData)[i].imag * 0.75;
}
} else {
for (int i = 0; i < stereoSize / 2; i++) {
ati_vis->inputRate = cModemKit->audioSampleRate;
ati_vis->sampleRate = 36000;
ati_vis->data[i] = ati->data[i * 2];
ati_vis->data[i + stereoSize / 2] = ati->data[i * 2 + 1];
}
}
ati_vis->type = 1;
} else {
size_t numAudioWritten = ati->data.size();
ati_vis->channels = 1;
std::vector<float> *demodOutData = (modemAnalog != nullptr)?modemAnalog->getDemodOutputData():nullptr;
if ((numAudioWritten > bufSize) || (demodOutData == nullptr)) {
ati_vis->inputRate = cModemKit->audioSampleRate;
if (num_vis > numAudioWritten) {
num_vis = numAudioWritten;
}
ati_vis->data.assign(ati->data.begin(), ati->data.begin() + num_vis);
} else {
if (num_vis > demodOutData->size()) {
num_vis = demodOutData->size();
}
ati_vis->data.assign(demodOutData->begin(), demodOutData->begin() + num_vis);
}
ati_vis->type = 0;
}
if (!localAudioVisOutputQueue->try_push(ati_vis)) {
//non-blocking push needed for audio vis out
std::cout << "DemodulatorThread::run() cannot push ati_vis into localAudioVisOutputQueue, is full !" << std::endl;
std::this_thread::yield();
}
}
if (!squelched && ati != nullptr) {
if (!muted && (!wxGetApp().getSoloMode() || (demodInstance ==
wxGetApp().getDemodMgr().getCurrentModem().get()))) {
//non-blocking push needed for audio out
if (!audioOutputQueue->try_push(ati)) {
std::cout << "DemodulatorThread::run() cannot push ati into audioOutputQueue, is full !" << std::endl;
std::this_thread::yield();
}
}
}
// Capture audioSinkOutputQueue state in a local variable
DemodulatorThreadOutputQueuePtr localAudioSinkOutputQueue = nullptr;
{
std::lock_guard < SpinMutex > lock(m_mutexAudioVisOutputQueue);
localAudioSinkOutputQueue = audioSinkOutputQueue;
}
//Push to audio sink, if any:
if (ati && localAudioSinkOutputQueue != nullptr) {
if (!localAudioSinkOutputQueue->try_push(ati)) {
std::cout << "DemodulatorThread::run() cannot push ati into audioSinkOutputQueue, is full !" << std::endl;
std::this_thread::yield();
}
}
}
// end while !stopping
// Purge any unused inputs, with a non-blocking pop
iqInputQueue->flush();
audioOutputQueue->flush();
// std::cout << "Demodulator thread done." << std::endl;
}
void DemodulatorThread::terminate() {
IOThread::terminate();
//unblock the currently blocked push()
iqInputQueue->flush();
audioOutputQueue->flush();
}
bool DemodulatorThread::isMuted() {
return muted;
}
void DemodulatorThread::setMuted(bool muted_in) {
muted = muted_in;
}
float DemodulatorThread::getSignalLevel() {
return signalLevel;
}
float DemodulatorThread::getSignalFloor() {
return signalFloor;
}
float DemodulatorThread::getSignalCeil() {
return signalCeil;
}
void DemodulatorThread::setSquelchEnabled(bool squelchEnabled_in) {
squelchEnabled = squelchEnabled_in;
}
bool DemodulatorThread::isSquelchEnabled() {
return squelchEnabled;
}
void DemodulatorThread::setSquelchLevel(float signal_level_in) {
if (!squelchEnabled) {
squelchEnabled = true;
}
squelchLevel = signal_level_in;
}
float DemodulatorThread::getSquelchLevel() {
return squelchLevel;
}
bool DemodulatorThread::getSquelchBreak() {
return squelchBreak;
}
void DemodulatorThread::releaseSquelchLock(DemodulatorInstance* inst) {
std::lock_guard < std::mutex > lock(squelchLockMutex);
if (inst == nullptr || squelchLock == inst) {
squelchLock = nullptr;
}
}

View File

@ -0,0 +1,75 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include <queue>
#include <vector>
#include <memory>
#include "DemodDefs.h"
#include "AudioThread.h"
#include "Modem.h"
#include "SpinMutex.h"
#define DEMOD_VIS_SIZE 2048
#define DEMOD_SIGNAL_MIN -30
#define DEMOD_SIGNAL_MAX 30
class DemodulatorInstance;
class DemodulatorThread : public IOThread {
public:
explicit DemodulatorThread(DemodulatorInstance* parent);
~DemodulatorThread() override;
void onBindOutput(std::string name, ThreadQueueBasePtr threadQueue) override;
void run() override;
void terminate() override;
void setMuted(bool muted_in);
bool isMuted();
float getSignalLevel();
float getSignalCeil();
void setSquelchEnabled(bool squelchEnabled_in);
bool isSquelchEnabled();
float getSignalFloor();
void setSquelchLevel(float signal_level_in);
float getSquelchLevel();
bool getSquelchBreak();
static void releaseSquelchLock(DemodulatorInstance* inst);
protected:
double abMagnitude(float inphase, float quadrature);
double linearToDb(double linear);
DemodulatorInstance* demodInstance;
ReBuffer<AudioThreadInput> outputBuffers;
std::atomic_bool muted;
std::atomic<float> squelchLevel;
std::atomic<float> signalLevel, signalFloor, signalCeil;
std::atomic<bool> squelchEnabled, squelchBreak;
static DemodulatorInstance* squelchLock;
static std::mutex squelchLockMutex;
Modem *cModem = nullptr;
ModemKit *cModemKit = nullptr;
DemodulatorThreadPostInputQueuePtr iqInputQueue;
AudioThreadInputQueuePtr audioOutputQueue;
DemodulatorThreadOutputQueuePtr audioVisOutputQueue;
DemodulatorThreadOutputQueuePtr audioSinkOutputQueue = nullptr;
//protects the audioVisOutputQueue dynamic binding change at runtime (in DemodulatorMgr)
SpinMutex m_mutexAudioVisOutputQueue;
};

View File

@ -0,0 +1,119 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "DemodulatorWorkerThread.h"
#include "CubicSDR.h"
//50 ms
#define HEARTBEAT_CHECK_PERIOD_MICROS (50 * 1000)
DemodulatorWorkerThread::DemodulatorWorkerThread() : IOThread(),
cModem(nullptr), cModemKit(nullptr) {
}
DemodulatorWorkerThread::~DemodulatorWorkerThread() = default;
void DemodulatorWorkerThread::run() {
// std::cout << "Demodulator worker thread started.." << std::endl;
commandQueue = std::static_pointer_cast<DemodulatorThreadWorkerCommandQueue>(getInputQueue("WorkerCommandQueue"));
resultQueue = std::static_pointer_cast<DemodulatorThreadWorkerResultQueue>(getOutputQueue("WorkerResultQueue"));
while (!stopping) {
bool filterChanged = false;
bool makeDemod = false;
DemodulatorWorkerThreadCommand filterCommand, demodCommand;
DemodulatorWorkerThreadCommand command;
bool done = false;
//Beware of the subtility here,
//we are waiting for the first command to show up (blocking!)
//then consuming the commands until done.
while (!done && !stopping) {
if (!commandQueue->pop(command, HEARTBEAT_CHECK_PERIOD_MICROS)) {
continue;
}
switch (command.cmd) {
case DemodulatorWorkerThreadCommand::Type::DEMOD_WORKER_THREAD_CMD_BUILD_FILTERS:
filterChanged = true;
filterCommand = command;
break;
case DemodulatorWorkerThreadCommand::Type::DEMOD_WORKER_THREAD_CMD_MAKE_DEMOD:
makeDemod = true;
demodCommand = command;
break;
default:
break;
}
done = commandQueue->empty();
} //end while done.
if ((makeDemod || filterChanged) && !stopping) {
DemodulatorWorkerThreadResult result(DemodulatorWorkerThreadResult::Type::DEMOD_WORKER_THREAD_RESULT_FILTERS);
if (filterCommand.sampleRate) {
result.sampleRate = filterCommand.sampleRate;
}
if (makeDemod) {
cModem = Modem::makeModem(demodCommand.demodType);
cModemName = cModem->getName();
cModemType = cModem->getType();
if (!demodCommand.settings.empty()) {
cModem->writeSettings(demodCommand.settings);
}
result.sampleRate = demodCommand.sampleRate;
wxGetApp().getAppFrame()->notifyUpdateModemProperties();
}
result.modem = cModem;
if (makeDemod && demodCommand.bandwidth && demodCommand.audioSampleRate) {
if (cModem != nullptr) {
result.bandwidth = cModem->checkSampleRate(demodCommand.bandwidth, demodCommand.audioSampleRate);
cModemKit = cModem->buildKit(result.bandwidth, demodCommand.audioSampleRate);
} else {
cModemKit = nullptr;
}
} else if (filterChanged && filterCommand.bandwidth && filterCommand.audioSampleRate) {
if (cModem != nullptr) {
result.bandwidth = cModem->checkSampleRate(filterCommand.bandwidth, filterCommand.audioSampleRate);
cModemKit = cModem->buildKit(result.bandwidth, filterCommand.audioSampleRate);
} else {
cModemKit = nullptr;
}
} else if (makeDemod) {
cModemKit = nullptr;
}
if (cModem != nullptr) {
cModem->clearRebuildKit();
}
float As = 60.0f; // stop-band attenuation [dB]
if (cModem && result.sampleRate && result.bandwidth) {
result.bandwidth = cModem->checkSampleRate(result.bandwidth, makeDemod?demodCommand.audioSampleRate:filterCommand.audioSampleRate);
result.iqResampleRatio = (double) (result.bandwidth) / (double) result.sampleRate;
result.iqResampler = msresamp_crcf_create(result.iqResampleRatio, As);
}
result.modemKit = cModemKit;
result.modemType = cModemType;
result.modemName = cModemName;
//VSO: blocking push
resultQueue->push(result);
}
}
// std::cout << "Demodulator worker thread done." << std::endl;
}
void DemodulatorWorkerThread::terminate() {
IOThread::terminate();
//unblock the push()
resultQueue->flush();
commandQueue->flush();
}

View File

@ -0,0 +1,102 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include <queue>
#include <vector>
#include <memory>
#include "liquid/liquid.h"
#include "AudioThread.h"
#include "ThreadBlockingQueue.h"
#include "CubicSDRDefs.h"
#include "Modem.h"
class DemodulatorWorkerThreadResult {
public:
enum class Type {
DEMOD_WORKER_THREAD_RESULT_NULL, DEMOD_WORKER_THREAD_RESULT_FILTERS
};
DemodulatorWorkerThreadResult() :
cmd(DemodulatorWorkerThreadResult::Type::DEMOD_WORKER_THREAD_RESULT_NULL), iqResampler(nullptr), iqResampleRatio(0), sampleRate(0), bandwidth(0), modemKit(nullptr) {
}
explicit DemodulatorWorkerThreadResult(DemodulatorWorkerThreadResult::Type cmd) :
DemodulatorWorkerThreadResult() {
this->cmd = cmd;
}
DemodulatorWorkerThreadResult::Type cmd;
msresamp_crcf iqResampler;
double iqResampleRatio;
long long sampleRate;
unsigned int bandwidth;
Modem *modem{};
ModemKit *modemKit;
std::string modemType;
std::string modemName;
};
class DemodulatorWorkerThreadCommand {
public:
enum class Type {
DEMOD_WORKER_THREAD_CMD_NULL, DEMOD_WORKER_THREAD_CMD_BUILD_FILTERS, DEMOD_WORKER_THREAD_CMD_MAKE_DEMOD
};
DemodulatorWorkerThreadCommand() :
cmd(DemodulatorWorkerThreadCommand::Type::DEMOD_WORKER_THREAD_CMD_NULL), frequency(0), sampleRate(0), bandwidth(0), audioSampleRate(0) {
}
explicit DemodulatorWorkerThreadCommand(DemodulatorWorkerThreadCommand::Type cmd) :
cmd(cmd), frequency(0), sampleRate(0), bandwidth(0), audioSampleRate(0) {
}
DemodulatorWorkerThreadCommand::Type cmd;
long long frequency;
long long sampleRate;
unsigned int bandwidth;
unsigned int audioSampleRate;
std::string demodType;
ModemSettings settings;
};
typedef ThreadBlockingQueue<DemodulatorWorkerThreadCommand> DemodulatorThreadWorkerCommandQueue;
typedef ThreadBlockingQueue<DemodulatorWorkerThreadResult> DemodulatorThreadWorkerResultQueue;
typedef std::shared_ptr<DemodulatorThreadWorkerCommandQueue> DemodulatorThreadWorkerCommandQueuePtr;
typedef std::shared_ptr<DemodulatorThreadWorkerResultQueue> DemodulatorThreadWorkerResultQueuePtr;
class DemodulatorWorkerThread : public IOThread {
public:
DemodulatorWorkerThread();
~DemodulatorWorkerThread() override;
void run() override;
void setCommandQueue(DemodulatorThreadWorkerCommandQueuePtr tQueue) {
commandQueue = tQueue;
}
void setResultQueue(DemodulatorThreadWorkerResultQueuePtr tQueue) {
resultQueue = tQueue;
}
void terminate() override;
protected:
DemodulatorThreadWorkerCommandQueuePtr commandQueue;
DemodulatorThreadWorkerResultQueuePtr resultQueue;
Modem *cModem;
ModemKit *cModemKit;
std::string cModemType;
std::string cModemName;
};

View File

@ -0,0 +1,143 @@
///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version Oct 26 2018)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
///////////////////////////////////////////////////////////////////////////
#include "BookmarkPanel.h"
///////////////////////////////////////////////////////////////////////////
BookmarkPanel::BookmarkPanel( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name ) : wxPanel( parent, id, pos, size, style, name )
{
wxBoxSizer* bSizer1;
bSizer1 = new wxBoxSizer( wxVERTICAL );
m_searchText = new wxTextCtrl( this, wxID_ANY, wxT("Search.."), wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER );
bSizer1->Add( m_searchText, 0, wxALL|wxEXPAND, 5 );
m_clearSearchButton = new wxButton( this, wxID_ANY, wxT("Clear Search"), wxDefaultPosition, wxDefaultSize, 0 );
bSizer1->Add( m_clearSearchButton, 0, wxALL|wxEXPAND, 5 );
m_treeView = new wxTreeCtrl( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTR_DEFAULT_STYLE|wxTR_HAS_VARIABLE_ROW_HEIGHT|wxTR_HIDE_ROOT|wxTR_SINGLE );
bSizer1->Add( m_treeView, 1, wxEXPAND, 5 );
m_propPanelDivider = new wxStaticLine( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
bSizer1->Add( m_propPanelDivider, 0, wxEXPAND, 5 );
m_propPanel = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
wxFlexGridSizer* fgPropSizer;
fgPropSizer = new wxFlexGridSizer( 0, 2, 0, 0 );
fgPropSizer->AddGrowableCol( 1 );
fgPropSizer->SetFlexibleDirection( wxBOTH );
fgPropSizer->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_SPECIFIED );
m_labelLabel = new wxStaticText( m_propPanel, wxID_ANY, wxT("Label"), wxDefaultPosition, wxDefaultSize, 0 );
m_labelLabel->Wrap( -1 );
fgPropSizer->Add( m_labelLabel, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
m_labelText = new wxTextCtrl( m_propPanel, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER );
fgPropSizer->Add( m_labelText, 0, wxALL|wxEXPAND, 5 );
m_frequencyLabel = new wxStaticText( m_propPanel, wxID_ANY, wxT("Freq"), wxDefaultPosition, wxDefaultSize, 0 );
m_frequencyLabel->Wrap( -1 );
fgPropSizer->Add( m_frequencyLabel, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxALL, 5 );
m_frequencyVal = new wxStaticText( m_propPanel, wxID_ANY, wxT("FrequencyVal"), wxDefaultPosition, wxDefaultSize, 0 );
m_frequencyVal->Wrap( -1 );
fgPropSizer->Add( m_frequencyVal, 0, wxALL|wxEXPAND, 5 );
m_bandwidthLabel = new wxStaticText( m_propPanel, wxID_ANY, wxT("BW"), wxDefaultPosition, wxDefaultSize, 0 );
m_bandwidthLabel->Wrap( -1 );
fgPropSizer->Add( m_bandwidthLabel, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxLEFT|wxRIGHT, 5 );
m_bandwidthVal = new wxStaticText( m_propPanel, wxID_ANY, wxT("BandwidthVal"), wxDefaultPosition, wxDefaultSize, 0 );
m_bandwidthVal->Wrap( -1 );
fgPropSizer->Add( m_bandwidthVal, 0, wxEXPAND|wxLEFT|wxRIGHT, 5 );
m_modulationLabel = new wxStaticText( m_propPanel, wxID_ANY, wxT("Type"), wxDefaultPosition, wxDefaultSize, 0 );
m_modulationLabel->Wrap( -1 );
fgPropSizer->Add( m_modulationLabel, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxALL, 5 );
m_modulationVal = new wxStaticText( m_propPanel, wxID_ANY, wxT("TypeVal"), wxDefaultPosition, wxDefaultSize, 0 );
m_modulationVal->Wrap( -1 );
fgPropSizer->Add( m_modulationVal, 0, wxALL|wxEXPAND, 5 );
m_propPanel->SetSizer( fgPropSizer );
m_propPanel->Layout();
fgPropSizer->Fit( m_propPanel );
bSizer1->Add( m_propPanel, 0, wxALL|wxEXPAND, 5 );
m_buttonPanel = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
wxBoxSizer* m_buttonPanelSizer;
m_buttonPanelSizer = new wxBoxSizer( wxVERTICAL );
m_buttonPanel->SetSizer( m_buttonPanelSizer );
m_buttonPanel->Layout();
m_buttonPanelSizer->Fit( m_buttonPanel );
bSizer1->Add( m_buttonPanel, 0, wxALL|wxEXPAND, 5 );
this->SetSizer( bSizer1 );
this->Layout();
m_updateTimer.SetOwner( this, wxID_ANY );
// Connect Events
this->Connect( wxEVT_ENTER_WINDOW, wxMouseEventHandler( BookmarkPanel::onEnterWindow ) );
this->Connect( wxEVT_LEAVE_WINDOW, wxMouseEventHandler( BookmarkPanel::onLeaveWindow ) );
this->Connect( wxEVT_MOTION, wxMouseEventHandler( BookmarkPanel::onMotion ) );
m_searchText->Connect( wxEVT_LEFT_DOWN, wxMouseEventHandler( BookmarkPanel::onSearchTextFocus ), NULL, this );
m_searchText->Connect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( BookmarkPanel::onSearchText ), NULL, this );
m_clearSearchButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( BookmarkPanel::onClearSearch ), NULL, this );
m_treeView->Connect( wxEVT_ENTER_WINDOW, wxMouseEventHandler( BookmarkPanel::onEnterWindow ), NULL, this );
m_treeView->Connect( wxEVT_KEY_UP, wxKeyEventHandler( BookmarkPanel::onKeyUp ), NULL, this );
m_treeView->Connect( wxEVT_LEAVE_WINDOW, wxMouseEventHandler( BookmarkPanel::onLeaveWindow ), NULL, this );
m_treeView->Connect( wxEVT_MOTION, wxMouseEventHandler( BookmarkPanel::onMotion ), NULL, this );
m_treeView->Connect( wxEVT_COMMAND_TREE_BEGIN_DRAG, wxTreeEventHandler( BookmarkPanel::onTreeBeginDrag ), NULL, this );
m_treeView->Connect( wxEVT_COMMAND_TREE_END_DRAG, wxTreeEventHandler( BookmarkPanel::onTreeEndDrag ), NULL, this );
m_treeView->Connect( wxEVT_COMMAND_TREE_ITEM_ACTIVATED, wxTreeEventHandler( BookmarkPanel::onTreeActivate ), NULL, this );
m_treeView->Connect( wxEVT_COMMAND_TREE_ITEM_COLLAPSED, wxTreeEventHandler( BookmarkPanel::onTreeCollapse ), NULL, this );
m_treeView->Connect( wxEVT_COMMAND_TREE_ITEM_EXPANDED, wxTreeEventHandler( BookmarkPanel::onTreeExpanded ), NULL, this );
m_treeView->Connect( wxEVT_COMMAND_TREE_ITEM_GETTOOLTIP, wxTreeEventHandler( BookmarkPanel::onTreeItemGetTooltip ), NULL, this );
m_treeView->Connect( wxEVT_COMMAND_TREE_ITEM_MENU, wxTreeEventHandler( BookmarkPanel::onTreeItemMenu ), NULL, this );
m_treeView->Connect( wxEVT_COMMAND_TREE_SEL_CHANGED, wxTreeEventHandler( BookmarkPanel::onTreeSelect ), NULL, this );
m_treeView->Connect( wxEVT_COMMAND_TREE_SEL_CHANGING, wxTreeEventHandler( BookmarkPanel::onTreeSelectChanging ), NULL, this );
m_labelText->Connect( wxEVT_KILL_FOCUS, wxFocusEventHandler( BookmarkPanel::onLabelKillFocus ), NULL, this );
m_labelText->Connect( wxEVT_COMMAND_TEXT_ENTER, wxCommandEventHandler( BookmarkPanel::onLabelText ), NULL, this );
m_frequencyVal->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( BookmarkPanel::onDoubleClickFreq ), NULL, this );
m_bandwidthVal->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( BookmarkPanel::onDoubleClickBandwidth ), NULL, this );
this->Connect( wxID_ANY, wxEVT_TIMER, wxTimerEventHandler( BookmarkPanel::onUpdateTimer ) );
}
BookmarkPanel::~BookmarkPanel()
{
// Disconnect Events
this->Disconnect( wxEVT_ENTER_WINDOW, wxMouseEventHandler( BookmarkPanel::onEnterWindow ) );
this->Disconnect( wxEVT_LEAVE_WINDOW, wxMouseEventHandler( BookmarkPanel::onLeaveWindow ) );
this->Disconnect( wxEVT_MOTION, wxMouseEventHandler( BookmarkPanel::onMotion ) );
m_searchText->Disconnect( wxEVT_LEFT_DOWN, wxMouseEventHandler( BookmarkPanel::onSearchTextFocus ), NULL, this );
m_searchText->Disconnect( wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler( BookmarkPanel::onSearchText ), NULL, this );
m_clearSearchButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( BookmarkPanel::onClearSearch ), NULL, this );
m_treeView->Disconnect( wxEVT_ENTER_WINDOW, wxMouseEventHandler( BookmarkPanel::onEnterWindow ), NULL, this );
m_treeView->Disconnect( wxEVT_KEY_UP, wxKeyEventHandler( BookmarkPanel::onKeyUp ), NULL, this );
m_treeView->Disconnect( wxEVT_LEAVE_WINDOW, wxMouseEventHandler( BookmarkPanel::onLeaveWindow ), NULL, this );
m_treeView->Disconnect( wxEVT_MOTION, wxMouseEventHandler( BookmarkPanel::onMotion ), NULL, this );
m_treeView->Disconnect( wxEVT_COMMAND_TREE_BEGIN_DRAG, wxTreeEventHandler( BookmarkPanel::onTreeBeginDrag ), NULL, this );
m_treeView->Disconnect( wxEVT_COMMAND_TREE_END_DRAG, wxTreeEventHandler( BookmarkPanel::onTreeEndDrag ), NULL, this );
m_treeView->Disconnect( wxEVT_COMMAND_TREE_ITEM_ACTIVATED, wxTreeEventHandler( BookmarkPanel::onTreeActivate ), NULL, this );
m_treeView->Disconnect( wxEVT_COMMAND_TREE_ITEM_COLLAPSED, wxTreeEventHandler( BookmarkPanel::onTreeCollapse ), NULL, this );
m_treeView->Disconnect( wxEVT_COMMAND_TREE_ITEM_EXPANDED, wxTreeEventHandler( BookmarkPanel::onTreeExpanded ), NULL, this );
m_treeView->Disconnect( wxEVT_COMMAND_TREE_ITEM_GETTOOLTIP, wxTreeEventHandler( BookmarkPanel::onTreeItemGetTooltip ), NULL, this );
m_treeView->Disconnect( wxEVT_COMMAND_TREE_ITEM_MENU, wxTreeEventHandler( BookmarkPanel::onTreeItemMenu ), NULL, this );
m_treeView->Disconnect( wxEVT_COMMAND_TREE_SEL_CHANGED, wxTreeEventHandler( BookmarkPanel::onTreeSelect ), NULL, this );
m_treeView->Disconnect( wxEVT_COMMAND_TREE_SEL_CHANGING, wxTreeEventHandler( BookmarkPanel::onTreeSelectChanging ), NULL, this );
m_labelText->Disconnect( wxEVT_KILL_FOCUS, wxFocusEventHandler( BookmarkPanel::onLabelKillFocus ), NULL, this );
m_labelText->Disconnect( wxEVT_COMMAND_TEXT_ENTER, wxCommandEventHandler( BookmarkPanel::onLabelText ), NULL, this );
m_frequencyVal->Disconnect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( BookmarkPanel::onDoubleClickFreq ), NULL, this );
m_bandwidthVal->Disconnect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( BookmarkPanel::onDoubleClickBandwidth ), NULL, this );
this->Disconnect( wxID_ANY, wxEVT_TIMER, wxTimerEventHandler( BookmarkPanel::onUpdateTimer ) );
}

View File

@ -0,0 +1,968 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<wxFormBuilder_Project>
<FileVersion major="1" minor="15" />
<object class="Project" expanded="1">
<property name="class_decoration"></property>
<property name="code_generation">C++</property>
<property name="disconnect_events">1</property>
<property name="disconnect_mode">source_name</property>
<property name="disconnect_php_events">0</property>
<property name="disconnect_python_events">0</property>
<property name="embedded_files_path">res</property>
<property name="encoding">UTF-8</property>
<property name="event_generation">connect</property>
<property name="file">BookmarkPanel</property>
<property name="first_id">1000</property>
<property name="help_provider">none</property>
<property name="indent_with_spaces"></property>
<property name="internationalize">0</property>
<property name="name">BookmarkPanel</property>
<property name="namespace"></property>
<property name="path">.</property>
<property name="precompiled_header"></property>
<property name="relative_path">1</property>
<property name="skip_lua_events">1</property>
<property name="skip_php_events">1</property>
<property name="skip_python_events">1</property>
<property name="ui_table">UI</property>
<property name="use_enum">0</property>
<property name="use_microsoft_bom">0</property>
<object class="Panel" expanded="1">
<property name="aui_managed">0</property>
<property name="aui_manager_style">wxAUI_MGR_DEFAULT</property>
<property name="bg"></property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="enabled">1</property>
<property name="event_handler">impl_virtual</property>
<property name="fg"></property>
<property name="font"></property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="maximum_size"></property>
<property name="minimum_size"></property>
<property name="name">BookmarkPanel</property>
<property name="pos"></property>
<property name="size">169,471</property>
<property name="subclass"></property>
<property name="tooltip"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style">wxTAB_TRAVERSAL</property>
<event name="OnEnterWindow">onEnterWindow</event>
<event name="OnLeaveWindow">onLeaveWindow</event>
<event name="OnMotion">onMotion</event>
<object class="wxBoxSizer" expanded="1">
<property name="minimum_size"></property>
<property name="name">bSizer1</property>
<property name="orient">wxVERTICAL</property>
<property name="permission">none</property>
<object class="sizeritem" expanded="0">
<property name="border">5</property>
<property name="flag">wxALL|wxEXPAND</property>
<property name="proportion">0</property>
<object class="wxTextCtrl" expanded="0">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="maxlength"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_searchText</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style">wxTE_PROCESS_ENTER</property>
<property name="subclass"></property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="validator_data_type"></property>
<property name="validator_style">wxFILTER_NONE</property>
<property name="validator_type">wxDefaultValidator</property>
<property name="validator_variable"></property>
<property name="value">Search..</property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<event name="OnLeftDown">onSearchTextFocus</event>
<event name="OnText">onSearchText</event>
</object>
</object>
<object class="sizeritem" expanded="0">
<property name="border">5</property>
<property name="flag">wxALL|wxEXPAND</property>
<property name="proportion">0</property>
<object class="wxButton" expanded="0">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="bitmap"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="current"></property>
<property name="default">0</property>
<property name="default_pane">0</property>
<property name="disabled"></property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="focus"></property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">Clear Search</property>
<property name="margins"></property>
<property name="markup">0</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_clearSearchButton</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="position"></property>
<property name="pressed"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style"></property>
<property name="subclass"></property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="validator_data_type"></property>
<property name="validator_style">wxFILTER_NONE</property>
<property name="validator_type">wxDefaultValidator</property>
<property name="validator_variable"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<event name="OnButtonClick">onClearSearch</event>
</object>
</object>
<object class="sizeritem" expanded="0">
<property name="border">5</property>
<property name="flag">wxEXPAND</property>
<property name="proportion">1</property>
<object class="wxTreeCtrl" expanded="0">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">0</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_treeView</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style">wxTR_DEFAULT_STYLE|wxTR_HAS_VARIABLE_ROW_HEIGHT|wxTR_HIDE_ROOT|wxTR_SINGLE</property>
<property name="subclass"></property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<event name="OnEnterWindow">onEnterWindow</event>
<event name="OnKeyUp">onKeyUp</event>
<event name="OnLeaveWindow">onLeaveWindow</event>
<event name="OnMotion">onMotion</event>
<event name="OnTreeBeginDrag">onTreeBeginDrag</event>
<event name="OnTreeEndDrag">onTreeEndDrag</event>
<event name="OnTreeItemActivated">onTreeActivate</event>
<event name="OnTreeItemCollapsed">onTreeCollapse</event>
<event name="OnTreeItemExpanded">onTreeExpanded</event>
<event name="OnTreeItemGetTooltip">onTreeItemGetTooltip</event>
<event name="OnTreeItemMenu">onTreeItemMenu</event>
<event name="OnTreeSelChanged">onTreeSelect</event>
<event name="OnTreeSelChanging">onTreeSelectChanging</event>
</object>
</object>
<object class="sizeritem" expanded="0">
<property name="border">5</property>
<property name="flag">wxEXPAND</property>
<property name="proportion">0</property>
<object class="wxStaticLine" expanded="0">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_propPanelDivider</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style">wxLI_HORIZONTAL</property>
<property name="subclass">; ; forward_declare</property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxALL|wxEXPAND</property>
<property name="proportion">0</property>
<object class="wxPanel" expanded="1">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_propPanel</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="subclass"></property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style">wxTAB_TRAVERSAL</property>
<object class="wxFlexGridSizer" expanded="0">
<property name="cols">2</property>
<property name="flexible_direction">wxBOTH</property>
<property name="growablecols">1</property>
<property name="growablerows"></property>
<property name="hgap">0</property>
<property name="minimum_size"></property>
<property name="name">fgPropSizer</property>
<property name="non_flexible_grow_mode">wxFLEX_GROWMODE_SPECIFIED</property>
<property name="permission">none</property>
<property name="rows">0</property>
<property name="vgap">0</property>
<object class="sizeritem" expanded="0">
<property name="border">5</property>
<property name="flag">wxALIGN_CENTER_VERTICAL|wxALL</property>
<property name="proportion">0</property>
<object class="wxStaticText" expanded="0">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">Label</property>
<property name="markup">0</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_labelLabel</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style"></property>
<property name="subclass"></property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<property name="wrap">-1</property>
</object>
</object>
<object class="sizeritem" expanded="0">
<property name="border">5</property>
<property name="flag">wxALL|wxEXPAND</property>
<property name="proportion">0</property>
<object class="wxTextCtrl" expanded="0">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="maxlength"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_labelText</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style">wxTE_PROCESS_ENTER</property>
<property name="subclass"></property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="validator_data_type"></property>
<property name="validator_style">wxFILTER_NONE</property>
<property name="validator_type">wxDefaultValidator</property>
<property name="validator_variable"></property>
<property name="value"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<event name="OnKillFocus">onLabelKillFocus</event>
<event name="OnTextEnter">onLabelText</event>
</object>
</object>
<object class="sizeritem" expanded="0">
<property name="border">5</property>
<property name="flag">wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxALL</property>
<property name="proportion">0</property>
<object class="wxStaticText" expanded="0">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">Freq</property>
<property name="markup">0</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_frequencyLabel</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style"></property>
<property name="subclass"></property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<property name="wrap">-1</property>
</object>
</object>
<object class="sizeritem" expanded="0">
<property name="border">5</property>
<property name="flag">wxALL|wxEXPAND</property>
<property name="proportion">0</property>
<object class="wxStaticText" expanded="0">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">FrequencyVal</property>
<property name="markup">0</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_frequencyVal</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style"></property>
<property name="subclass"></property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<property name="wrap">-1</property>
<event name="OnLeftDClick">onDoubleClickFreq</event>
</object>
</object>
<object class="sizeritem" expanded="0">
<property name="border">5</property>
<property name="flag">wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxLEFT|wxRIGHT</property>
<property name="proportion">0</property>
<object class="wxStaticText" expanded="0">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">BW</property>
<property name="markup">0</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_bandwidthLabel</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style"></property>
<property name="subclass"></property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<property name="wrap">-1</property>
</object>
</object>
<object class="sizeritem" expanded="0">
<property name="border">5</property>
<property name="flag">wxEXPAND|wxLEFT|wxRIGHT</property>
<property name="proportion">0</property>
<object class="wxStaticText" expanded="0">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">BandwidthVal</property>
<property name="markup">0</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_bandwidthVal</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style"></property>
<property name="subclass"></property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<property name="wrap">-1</property>
<event name="OnLeftDClick">onDoubleClickBandwidth</event>
</object>
</object>
<object class="sizeritem" expanded="0">
<property name="border">5</property>
<property name="flag">wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT|wxALL</property>
<property name="proportion">0</property>
<object class="wxStaticText" expanded="0">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">Type</property>
<property name="markup">0</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_modulationLabel</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style"></property>
<property name="subclass"></property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<property name="wrap">-1</property>
</object>
</object>
<object class="sizeritem" expanded="0">
<property name="border">5</property>
<property name="flag">wxALL|wxEXPAND</property>
<property name="proportion">0</property>
<object class="wxStaticText" expanded="0">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">TypeVal</property>
<property name="markup">0</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_modulationVal</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style"></property>
<property name="subclass"></property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<property name="wrap">-1</property>
</object>
</object>
</object>
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxALL|wxEXPAND</property>
<property name="proportion">0</property>
<object class="wxPanel" expanded="1">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_buttonPanel</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="subclass"></property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style">wxTAB_TRAVERSAL</property>
<object class="wxBoxSizer" expanded="0">
<property name="minimum_size"></property>
<property name="name">m_buttonPanelSizer</property>
<property name="orient">wxVERTICAL</property>
<property name="permission">none</property>
</object>
</object>
</object>
</object>
<object class="wxTimer" expanded="0">
<property name="enabled">0</property>
<property name="id">wxID_ANY</property>
<property name="name">m_updateTimer</property>
<property name="oneshot">0</property>
<property name="period">500</property>
<property name="permission">protected</property>
<event name="OnTimer">onUpdateTimer</event>
</object>
</object>
</object>
</wxFormBuilder_Project>

View File

@ -0,0 +1,86 @@
///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version Oct 26 2018)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
///////////////////////////////////////////////////////////////////////////
#pragma once
#include <wx/artprov.h>
#include <wx/xrc/xmlres.h>
#include <wx/string.h>
#include <wx/textctrl.h>
#include <wx/gdicmn.h>
#include <wx/font.h>
#include <wx/colour.h>
#include <wx/settings.h>
#include <wx/bitmap.h>
#include <wx/image.h>
#include <wx/icon.h>
#include <wx/button.h>
#include <wx/treectrl.h>
#include <wx/statline.h>
#include <wx/stattext.h>
#include <wx/sizer.h>
#include <wx/panel.h>
#include <wx/timer.h>
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
/// Class BookmarkPanel
///////////////////////////////////////////////////////////////////////////////
class BookmarkPanel : public wxPanel
{
private:
protected:
wxTextCtrl* m_searchText;
wxButton* m_clearSearchButton;
wxTreeCtrl* m_treeView;
wxStaticLine* m_propPanelDivider;
wxPanel* m_propPanel;
wxStaticText* m_labelLabel;
wxTextCtrl* m_labelText;
wxStaticText* m_frequencyLabel;
wxStaticText* m_frequencyVal;
wxStaticText* m_bandwidthLabel;
wxStaticText* m_bandwidthVal;
wxStaticText* m_modulationLabel;
wxStaticText* m_modulationVal;
wxPanel* m_buttonPanel;
wxTimer m_updateTimer;
// Virtual event handlers, override them in your derived class
virtual void onEnterWindow( wxMouseEvent& event ) { event.Skip(); }
virtual void onLeaveWindow( wxMouseEvent& event ) { event.Skip(); }
virtual void onMotion( wxMouseEvent& event ) { event.Skip(); }
virtual void onSearchTextFocus( wxMouseEvent& event ) { event.Skip(); }
virtual void onSearchText( wxCommandEvent& event ) { event.Skip(); }
virtual void onClearSearch( wxCommandEvent& event ) { event.Skip(); }
virtual void onKeyUp( wxKeyEvent& event ) { event.Skip(); }
virtual void onTreeBeginDrag( wxTreeEvent& event ) { event.Skip(); }
virtual void onTreeEndDrag( wxTreeEvent& event ) { event.Skip(); }
virtual void onTreeActivate( wxTreeEvent& event ) { event.Skip(); }
virtual void onTreeCollapse( wxTreeEvent& event ) { event.Skip(); }
virtual void onTreeExpanded( wxTreeEvent& event ) { event.Skip(); }
virtual void onTreeItemGetTooltip( wxTreeEvent& event ) { event.Skip(); }
virtual void onTreeItemMenu( wxTreeEvent& event ) { event.Skip(); }
virtual void onTreeSelect( wxTreeEvent& event ) { event.Skip(); }
virtual void onTreeSelectChanging( wxTreeEvent& event ) { event.Skip(); }
virtual void onLabelKillFocus( wxFocusEvent& event ) { event.Skip(); }
virtual void onLabelText( wxCommandEvent& event ) { event.Skip(); }
virtual void onDoubleClickFreq( wxMouseEvent& event ) { event.Skip(); }
virtual void onDoubleClickBandwidth( wxMouseEvent& event ) { event.Skip(); }
virtual void onUpdateTimer( wxTimerEvent& event ) { event.Skip(); }
public:
BookmarkPanel( wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 169,471 ), long style = wxTAB_TRAVERSAL, const wxString& name = wxEmptyString );
~BookmarkPanel();
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,203 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "wx/choice.h"
#include "wx/dialog.h"
#include "BookmarkPanel.h"
#include "BookmarkMgr.h"
#include "MouseTracker.h"
class TreeViewItem : public wxTreeItemData {
public:
enum class Type {
TREEVIEW_ITEM_TYPE_GROUP,
TREEVIEW_ITEM_TYPE_ACTIVE,
TREEVIEW_ITEM_TYPE_RECENT,
TREEVIEW_ITEM_TYPE_BOOKMARK,
TREEVIEW_ITEM_TYPE_RANGE
};
TreeViewItem() {
demod = nullptr;
bookmarkEnt = nullptr;
rangeEnt = nullptr;
};
// copy constructor
TreeViewItem(const TreeViewItem& src) {
demod = src.demod;
bookmarkEnt = src.bookmarkEnt;
rangeEnt = src.rangeEnt;
type = src.type;
groupName = src.groupName;
};
~TreeViewItem() override = default;
TreeViewItem::Type type;
BookmarkEntryPtr bookmarkEnt;
BookmarkRangeEntryPtr rangeEnt;
DemodulatorInstancePtr demod;
std::string groupName;
};
class BookmarkViewVisualDragItem : public wxDialog {
public:
explicit BookmarkViewVisualDragItem(const wxString& labelValue = L"Popup");
};
class BookmarkView : public BookmarkPanel {
public:
explicit BookmarkView( wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1, -1 ), long style = wxTAB_TRAVERSAL );
~BookmarkView() override;
//order an asynchronous refresh/rebuild of the whole tree,
//will take effect at the next onUpdateTimer() occurrence.
void updateActiveList();
//order asynchronous updates of the bookmarks,
//will take effect at the next onUpdateTimer() occurrence.
void updateBookmarks();
void updateBookmarks(const std::string& group);
bool isKeywordMatch(std::wstring str, std::vector<std::wstring> &keywords);
wxTreeItemId refreshBookmarks();
void updateTheme();
void onMenuItem(wxCommandEvent& event);
bool isMouseInView();
bool getExpandState(const std::string& branchName);
void setExpandState(const std::string& branchName, bool state);
static BookmarkRangeEntryPtr makeActiveRangeEntry();
protected:
void activeSelection(const DemodulatorInstancePtr& dsel);
void bookmarkSelection(const BookmarkEntryPtr& bmSel);
void rangeSelection(const BookmarkRangeEntryPtr& re);
void activateBookmark(const BookmarkEntryPtr& bmEnt);
void activateRange(const BookmarkRangeEntryPtr& rangeEnt);
void recentSelection(const BookmarkEntryPtr& bmSel);
void groupSelection(const std::string& groupName);
void bookmarkBranchSelection();
void recentBranchSelection();
void rangeBranchSelection();
void activeBranchSelection();
void ensureSelectionInView();
void hideProps(bool hidePanel = true);
void showProps();
void onUpdateTimer( wxTimerEvent& event ) override;
//refresh / rebuild the whole tree item immediately
void doUpdateActiveList();
void onKeyUp( wxKeyEvent& event ) override;
void onTreeActivate( wxTreeEvent& event ) override;
void onTreeCollapse( wxTreeEvent& event ) override;
void onTreeExpanded( wxTreeEvent& event ) override;
void onTreeItemMenu( wxTreeEvent& event ) override;
void onTreeSelect( wxTreeEvent& event ) override;
void onTreeSelectChanging( wxTreeEvent& event ) override;
void onLabelKillFocus(wxFocusEvent& event ) override;
void onLabelText( wxCommandEvent& event ) override;
void onDoubleClickFreq( wxMouseEvent& event ) override;
void onDoubleClickBandwidth( wxMouseEvent& event ) override;
void onTreeBeginDrag( wxTreeEvent& event ) override;
void onTreeEndDrag( wxTreeEvent& event ) override;
void onTreeItemGetTooltip( wxTreeEvent& event ) override;
void onEnterWindow( wxMouseEvent& event ) override;
void onLeaveWindow( wxMouseEvent& event ) override;
void onMotion( wxMouseEvent& event ) override;
void onSearchTextFocus( wxMouseEvent& event ) override;
void onSearchText( wxCommandEvent& event ) override;
void onClearSearch( wxCommandEvent& event ) override;
void clearButtons();
void showButtons();
void refreshLayout();
wxButton *makeButton(wxWindow *parent, const std::string& labelVal, wxObjectEventFunction handler);
wxButton *addButton(wxWindow *parent, const std::string& labelVal, wxObjectEventFunction handler);
void doBookmarkActive(const std::string& group, const DemodulatorInstancePtr& demod);
void doBookmarkRecent(const std::string& group, const BookmarkEntryPtr& be);
void doMoveBookmark(const BookmarkEntryPtr& be, const std::string& group);
void doRemoveActive(const DemodulatorInstancePtr& demod);
void doRemoveRecent(const BookmarkEntryPtr& be);
void doClearRecents();
void updateBookmarkChoices();
void addBookmarkChoice(wxWindow *parent);
void onBookmarkChoice( wxCommandEvent &event );
void onRemoveActive( wxCommandEvent& event );
void onStartRecording( wxCommandEvent& event );
void onStopRecording( wxCommandEvent& event );
void onRemoveBookmark( wxCommandEvent& event );
void onActivateBookmark( wxCommandEvent& event );
void onActivateRecent( wxCommandEvent& event );
void onRemoveRecent ( wxCommandEvent& event );
void onClearRecents ( wxCommandEvent& event );
void onAddGroup( wxCommandEvent& event );
void onRemoveGroup( wxCommandEvent& event );
void onAddRange( wxCommandEvent& event );
void onRemoveRange( wxCommandEvent& event );
void onRenameRange( wxCommandEvent& event );
void onActivateRange( wxCommandEvent& event );
void onUpdateRange( wxCommandEvent& event );
bool skipEvents();
TreeViewItem *itemToTVI(wxTreeItemId item);
void SetTreeItemData(const wxTreeItemId& item, wxTreeItemData *data);
MouseTracker mouseTracker;
wxTreeItemId rootBranch, activeBranch, bookmarkBranch, recentBranch, rangeBranch;
std::map<std::string, bool> expandState;
TreeViewItem *dragItem;
wxTreeItemId dragItemId;
BookmarkViewVisualDragItem *visualDragItem;
// Bookmarks
std::atomic_bool doUpdateBookmarks;
std::set< std::string > doUpdateBookmarkGroup;
std::map<std::string, wxTreeItemId> groups;
wxArrayString bookmarkChoices;
wxChoice *bookmarkChoice;
// Active
std::atomic_bool doUpdateActive;
// Focus
BookmarkEntryPtr nextEnt;
BookmarkRangeEntryPtr nextRange;
DemodulatorInstancePtr nextDemod;
std::string nextGroup;
// Search
std::vector<std::wstring> searchKeywords;
void setStatusText(const std::string& statusText);
};

View File

@ -0,0 +1,9 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "AboutDialog.h"
AboutDialog::AboutDialog( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style)
: AboutDialogBase(parent, id, title, pos, size, style) {
m_appName->SetLabelText(CUBICSDR_INSTALL_NAME " v" CUBICSDR_VERSION);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "AboutDialogBase.h"
#include "CubicSDRDefs.h"
class AboutDialog : public AboutDialogBase {
public:
explicit AboutDialog( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("About"),
const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 530, 420 ),
long style = wxDEFAULT_DIALOG_STYLE );
};

View File

@ -0,0 +1,700 @@
///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version Oct 26 2018)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
///////////////////////////////////////////////////////////////////////////
#include "AboutDialogBase.h"
///////////////////////////////////////////////////////////////////////////
AboutDialogBase::AboutDialogBase( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style )
{
this->SetSizeHints( wxDefaultSize, wxDefaultSize );
wxBoxSizer* dlgSizer;
dlgSizer = new wxBoxSizer( wxVERTICAL );
m_hPanel = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
wxBoxSizer* m_hSizer;
m_hSizer = new wxBoxSizer( wxHORIZONTAL );
m_hSizer->SetMinSize( wxSize( -1,42 ) );
m_appName = new wxStaticText( m_hPanel, wxID_ANY, wxT("CubicSDR"), wxDefaultPosition, wxDefaultSize, 0 );
m_appName->Wrap( -1 );
m_appName->SetFont( wxFont( 20, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) );
m_hSizer->Add( m_appName, 1, wxALL|wxEXPAND, 6 );
m_hPanel->SetSizer( m_hSizer );
m_hPanel->Layout();
m_hSizer->Fit( m_hPanel );
dlgSizer->Add( m_hPanel, 0, wxALL|wxEXPAND, 5 );
m_aboutNotebook = new wxNotebook( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 );
m_dbPanel = new wxPanel( m_aboutNotebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
wxBoxSizer* m_dbScrollSizer;
m_dbScrollSizer = new wxBoxSizer( wxVERTICAL );
m_dbScroll = new wxScrolledWindow( m_dbPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL );
m_dbScroll->SetScrollRate( 5, 5 );
wxBoxSizer* m_dbPane;
m_dbPane = new wxBoxSizer( wxVERTICAL );
m_dbPanel1 = new wxPanel( m_dbScroll, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
wxFlexGridSizer* m_dbSizer;
m_dbSizer = new wxFlexGridSizer( 0, 3, 2, 20 );
m_dbSizer->SetFlexibleDirection( wxBOTH );
m_dbSizer->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_ALL );
m_dbHeader = new wxStaticText( m_dbPanel1, wxID_ANY, wxT("Developed By"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE );
m_dbHeader->Wrap( -1 );
m_dbHeader->SetFont( wxFont( 15, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) );
m_dbSizer->Add( m_dbHeader, 0, wxALL, 5 );
m_dbGHHeader = new wxStaticText( m_dbPanel1, wxID_ANY, wxT("GitHub"), wxDefaultPosition, wxDefaultSize, 0 );
m_dbGHHeader->Wrap( -1 );
m_dbGHHeader->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) );
m_dbSizer->Add( m_dbGHHeader, 0, wxALL, 5 );
m_dbTwitter = new wxStaticText( m_dbPanel1, wxID_ANY, wxT("Twitter"), wxDefaultPosition, wxDefaultSize, 0 );
m_dbTwitter->Wrap( -1 );
m_dbTwitter->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) );
m_dbSizer->Add( m_dbTwitter, 0, wxALL, 5 );
m_dbCharlesCliffe = new wxStaticText( m_dbPanel1, wxID_ANY, wxT("Charles J. Cliffe"), wxDefaultPosition, wxDefaultSize, 0 );
m_dbCharlesCliffe->Wrap( -1 );
m_dbSizer->Add( m_dbCharlesCliffe, 0, wxALL, 5 );
m_dbghCC = new wxStaticText( m_dbPanel1, wxID_ANY, wxT("@cjcliffe"), wxDefaultPosition, wxDefaultSize, 0 );
m_dbghCC->Wrap( -1 );
m_dbSizer->Add( m_dbghCC, 0, wxALL, 5 );
m_dbtCC = new wxStaticText( m_dbPanel1, wxID_ANY, wxT("@ccliffe"), wxDefaultPosition, wxDefaultSize, 0 );
m_dbtCC->Wrap( -1 );
m_dbSizer->Add( m_dbtCC, 0, wxALL, 5 );
m_dbVincentSonnier = new wxStaticText( m_dbPanel1, wxID_ANY, wxT("Vincent Sonnier"), wxDefaultPosition, wxDefaultSize, 0 );
m_dbVincentSonnier->Wrap( -1 );
m_dbSizer->Add( m_dbVincentSonnier, 0, wxALL, 5 );
m_dbghVS = new wxStaticText( m_dbPanel1, wxID_ANY, wxT("@vsonnier"), wxDefaultPosition, wxDefaultSize, 0 );
m_dbghVS->Wrap( -1 );
m_dbSizer->Add( m_dbghVS, 0, wxALL, 5 );
m_dbtVS = new wxStaticText( m_dbPanel1, wxID_ANY, wxT("@VincentSonnier"), wxDefaultPosition, wxDefaultSize, 0 );
m_dbtVS->Wrap( -1 );
m_dbSizer->Add( m_dbtVS, 0, wxALL, 5 );
m_dbPanel1->SetSizer( m_dbSizer );
m_dbPanel1->Layout();
m_dbSizer->Fit( m_dbPanel1 );
m_dbPane->Add( m_dbPanel1, 0, wxALL|wxEXPAND|wxRESERVE_SPACE_EVEN_IF_HIDDEN, 5 );
m_dbDivider1 = new wxStaticLine( m_dbScroll, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
m_dbPane->Add( m_dbDivider1, 0, wxALL|wxEXPAND|wxRESERVE_SPACE_EVEN_IF_HIDDEN, 10 );
m_dbPanel2 = new wxPanel( m_dbScroll, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
wxFlexGridSizer* m_cSizer;
m_cSizer = new wxFlexGridSizer( 0, 2, 2, 20 );
m_cSizer->SetFlexibleDirection( wxBOTH );
m_cSizer->SetNonFlexibleGrowMode( wxFLEX_GROWMODE_ALL );
m_cContributorsHeader = new wxStaticText( m_dbPanel2, wxID_ANY, wxT("Contributors"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE );
m_cContributorsHeader->Wrap( -1 );
m_cContributorsHeader->SetFont( wxFont( 15, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) );
m_cSizer->Add( m_cContributorsHeader, 0, wxALL, 5 );
m_cGitHub = new wxStaticText( m_dbPanel2, wxID_ANY, wxT("GitHub"), wxDefaultPosition, wxDefaultSize, 0 );
m_cGitHub->Wrap( -1 );
m_cGitHub->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) );
m_cSizer->Add( m_cGitHub, 0, wxALL, 5 );
m_cCorneLukken = new wxStaticText( m_dbPanel2, wxID_ANY, wxT("Corne Lukken"), wxDefaultPosition, wxDefaultSize, 0 );
m_cCorneLukken->Wrap( -1 );
m_cSizer->Add( m_cCorneLukken, 0, wxALL, 5 );
m_cghCL = new wxStaticText( m_dbPanel2, wxID_ANY, wxT("@Dantali0n"), wxDefaultPosition, wxDefaultSize, 0 );
m_cghCL->Wrap( -1 );
m_cSizer->Add( m_cghCL, 0, wxALL, 5 );
m_cStainislawPitucha = new wxStaticText( m_dbPanel2, wxID_ANY, wxT("Stanisław Pitucha"), wxDefaultPosition, wxDefaultSize, 0 );
m_cStainislawPitucha->Wrap( -1 );
m_cSizer->Add( m_cStainislawPitucha, 0, wxALL, 5 );
m_cghSP = new wxStaticText( m_dbPanel2, wxID_ANY, wxT("@viraptor"), wxDefaultPosition, wxDefaultSize, 0 );
m_cghSP->Wrap( -1 );
m_cSizer->Add( m_cghSP, 0, wxALL, 5 );
m_cTomSwartz = new wxStaticText( m_dbPanel2, wxID_ANY, wxT("Tom Swartz"), wxDefaultPosition, wxDefaultSize, 0 );
m_cTomSwartz->Wrap( -1 );
m_cSizer->Add( m_cTomSwartz, 0, wxALL, 5 );
m_cghTS = new wxStaticText( m_dbPanel2, wxID_ANY, wxT("@tomswartz07"), wxDefaultPosition, wxDefaultSize, 0 );
m_cghTS->Wrap( -1 );
m_cSizer->Add( m_cghTS, 0, wxALL, 5 );
m_cStefanTalpalaru = new wxStaticText( m_dbPanel2, wxID_ANY, wxT("Ștefan Talpalaru"), wxDefaultPosition, wxDefaultSize, 0 );
m_cStefanTalpalaru->Wrap( -1 );
m_cSizer->Add( m_cStefanTalpalaru, 0, wxALL, 5 );
m_cghST = new wxStaticText( m_dbPanel2, wxID_ANY, wxT("@stefantalpalaru"), wxDefaultPosition, wxDefaultSize, 0 );
m_cghST->Wrap( -1 );
m_cSizer->Add( m_cghST, 0, wxALL, 5 );
m_cDellRaySackett = new wxStaticText( m_dbPanel2, wxID_ANY, wxT("Dell-Ray Sackett"), wxDefaultPosition, wxDefaultSize, 0 );
m_cDellRaySackett->Wrap( -1 );
m_cSizer->Add( m_cDellRaySackett, 0, wxALL, 5 );
m_cghDRS = new wxStaticText( m_dbPanel2, wxID_ANY, wxT("@lospheris"), wxDefaultPosition, wxDefaultSize, 0 );
m_cghDRS->Wrap( -1 );
m_cSizer->Add( m_cghDRS, 0, wxALL, 5 );
m_cJiangWei = new wxStaticText( m_dbPanel2, wxID_ANY, wxT("Jiang Wei"), wxDefaultPosition, wxDefaultSize, 0 );
m_cJiangWei->Wrap( -1 );
m_cSizer->Add( m_cJiangWei, 0, wxALL, 5 );
m_cghJW = new wxStaticText( m_dbPanel2, wxID_ANY, wxT("@jocover"), wxDefaultPosition, wxDefaultSize, 0 );
m_cghJW->Wrap( -1 );
m_cSizer->Add( m_cghJW, 0, wxALL, 5 );
m_cInfinityCyberworks = new wxStaticText( m_dbPanel2, wxID_ANY, wxT("Infinity Cyberworks"), wxDefaultPosition, wxDefaultSize, 0 );
m_cInfinityCyberworks->Wrap( -1 );
m_cSizer->Add( m_cInfinityCyberworks, 0, wxALL, 5 );
m_cghIC = new wxStaticText( m_dbPanel2, wxID_ANY, wxT("@infinitycyberworks"), wxDefaultPosition, wxDefaultSize, 0 );
m_cghIC->Wrap( -1 );
m_cSizer->Add( m_cghIC, 0, wxALL, 5 );
m_cCrisMotch = new wxStaticText( m_dbPanel2, wxID_ANY, wxT("Chris Motch"), wxDefaultPosition, wxDefaultSize, 0 );
m_cCrisMotch->Wrap( -1 );
m_cSizer->Add( m_cCrisMotch, 0, wxALL, 5 );
m_cghCM = new wxStaticText( m_dbPanel2, wxID_ANY, wxT("@bodrick"), wxDefaultPosition, wxDefaultSize, 0 );
m_cghCM->Wrap( -1 );
m_cSizer->Add( m_cghCM, 0, wxALL, 5 );
m_cAntiHax = new wxStaticText( m_dbPanel2, wxID_ANY, wxT("Antihax"), wxDefaultPosition, wxDefaultSize, 0 );
m_cAntiHax->Wrap( -1 );
m_cSizer->Add( m_cAntiHax, 0, wxALL, 5 );
m_cghAH = new wxStaticText( m_dbPanel2, wxID_ANY, wxT("@antihax"), wxDefaultPosition, wxDefaultSize, 0 );
m_cghAH->Wrap( -1 );
m_cSizer->Add( m_cghAH, 0, wxALL, 5 );
m_cRainbow = new wxStaticText( m_dbPanel2, wxID_ANY, wxT("Rainbow"), wxDefaultPosition, wxDefaultSize, 0 );
m_cRainbow->Wrap( -1 );
m_cSizer->Add( m_cRainbow, 0, wxALL, 5 );
m_cghRBW = new wxStaticText( m_dbPanel2, wxID_ANY, wxT("@ra1nb0w"), wxDefaultPosition, wxDefaultSize, 0 );
m_cghRBW->Wrap( -1 );
m_cSizer->Add( m_cghRBW, 0, wxALL, 5 );
m_cMariuszRyndzionek = new wxStaticText( m_dbPanel2, wxID_ANY, wxT("Mariusz Ryndzionek"), wxDefaultPosition, wxDefaultSize, 0 );
m_cMariuszRyndzionek->Wrap( -1 );
m_cSizer->Add( m_cMariuszRyndzionek, 0, wxALL, 5 );
m_cghMR = new wxStaticText( m_dbPanel2, wxID_ANY, wxT("@mryndzionek"), wxDefaultPosition, wxDefaultSize, 0 );
m_cghMR->Wrap( -1 );
m_cSizer->Add( m_cghMR, 0, wxALL, 5 );
m_cDrahosj = new wxStaticText( m_dbPanel2, wxID_ANY, wxT("drahosj"), wxDefaultPosition, wxDefaultSize, 0 );
m_cDrahosj->Wrap( -1 );
m_cSizer->Add( m_cDrahosj, 0, wxALL, 5 );
m_cghDra = new wxStaticText( m_dbPanel2, wxID_ANY, wxT("@drahosj"), wxDefaultPosition, wxDefaultSize, 0 );
m_cghDra->Wrap( -1 );
m_cSizer->Add( m_cghDra, 0, wxALL, 5 );
m_cBenoitAllard = new wxStaticText( m_dbPanel2, wxID_ANY, wxT("Benoît Allard"), wxDefaultPosition, wxDefaultSize, 0 );
m_cBenoitAllard->Wrap( -1 );
m_cSizer->Add( m_cBenoitAllard, 0, wxALL, 5 );
m_cghBA = new wxStaticText( m_dbPanel2, wxID_ANY, wxT("@benallard"), wxDefaultPosition, wxDefaultSize, 0 );
m_cghBA->Wrap( -1 );
m_cSizer->Add( m_cghBA, 0, wxALL, 5 );
m_cDianeBruce = new wxStaticText( m_dbPanel2, wxID_ANY, wxT("Diane Bruce"), wxDefaultPosition, wxDefaultSize, 0 );
m_cDianeBruce->Wrap( -1 );
m_cSizer->Add( m_cDianeBruce, 0, wxALL, 5 );
m_cghDiB = new wxStaticText( m_dbPanel2, wxID_ANY, wxT("@DianeBruce"), wxDefaultPosition, wxDefaultSize, 0 );
m_cghDiB->Wrap( -1 );
m_cSizer->Add( m_cghDiB, 0, wxALL, 5 );
m_dbPanel2->SetSizer( m_cSizer );
m_dbPanel2->Layout();
m_cSizer->Fit( m_dbPanel2 );
m_dbPane->Add( m_dbPanel2, 1, wxALL|wxEXPAND|wxRESERVE_SPACE_EVEN_IF_HIDDEN, 5 );
m_dbScroll->SetSizer( m_dbPane );
m_dbScroll->Layout();
m_dbPane->Fit( m_dbScroll );
m_dbScrollSizer->Add( m_dbScroll, 1, wxEXPAND | wxALL, 5 );
m_dbPanel->SetSizer( m_dbScrollSizer );
m_dbPanel->Layout();
m_dbScrollSizer->Fit( m_dbPanel );
m_aboutNotebook->AddPage( m_dbPanel, wxT("Developers"), true );
m_dPanel = new wxPanel( m_aboutNotebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
wxBoxSizer* m_dScrollSizer;
m_dScrollSizer = new wxBoxSizer( wxVERTICAL );
m_dScroll = new wxScrolledWindow( m_dPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL );
m_dScroll->SetScrollRate( 5, 5 );
wxBoxSizer* m_dSizer;
m_dSizer = new wxBoxSizer( wxVERTICAL );
m_dHeader = new wxStaticText( m_dScroll, wxID_ANY, wxT("Thanks to everyone who donated at cubicsdr.com!"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE );
m_dHeader->Wrap( -1 );
m_dHeader->SetFont( wxFont( 14, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) );
m_dSizer->Add( m_dHeader, 0, wxALL, 5 );
m_dDivider1 = new wxStaticLine( m_dScroll, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
m_dSizer->Add( m_dDivider1, 0, wxEXPAND | wxALL, 5 );
m_dSDRplay = new wxStaticText( m_dScroll, wxID_ANY, wxT("SDRplay / sdrplay.com"), wxDefaultPosition, wxDefaultSize, 0 );
m_dSDRplay->Wrap( -1 );
m_dSizer->Add( m_dSDRplay, 0, wxALL, 5 );
m_dMichaelLadd = new wxStaticText( m_dScroll, wxID_ANY, wxT("Michael Ladd"), wxDefaultPosition, wxDefaultSize, 0 );
m_dMichaelLadd->Wrap( -1 );
m_dSizer->Add( m_dMichaelLadd, 0, wxALL, 5 );
m_dAutoMotiveTemplates = new wxStaticText( m_dScroll, wxID_ANY, wxT("Automotive Templates"), wxDefaultPosition, wxDefaultSize, 0 );
m_dAutoMotiveTemplates->Wrap( -1 );
m_dSizer->Add( m_dAutoMotiveTemplates, 0, wxALL, 5 );
m_dJorgeMorales = new wxStaticText( m_dScroll, wxID_ANY, wxT("Jorge Morales"), wxDefaultPosition, wxDefaultSize, 0 );
m_dJorgeMorales->Wrap( -1 );
m_dSizer->Add( m_dJorgeMorales, 0, wxALL, 5 );
m_dMichaelRooke = new wxStaticText( m_dScroll, wxID_ANY, wxT("Michael Rooke"), wxDefaultPosition, wxDefaultSize, 0 );
m_dMichaelRooke->Wrap( -1 );
m_dSizer->Add( m_dMichaelRooke, 0, wxALL, 5 );
m_dTNCOM = new wxStaticText( m_dScroll, wxID_ANY, wxT("TNCOM"), wxDefaultPosition, wxDefaultSize, 0 );
m_dTNCOM->Wrap( -1 );
m_dSizer->Add( m_dTNCOM, 0, wxALL, 5 );
m_dErikWied = new wxStaticText( m_dScroll, wxID_ANY, wxT("Erik Mikkel Wied"), wxDefaultPosition, wxDefaultSize, 0 );
m_dErikWied->Wrap( -1 );
m_dSizer->Add( m_dErikWied, 0, wxALL, 5 );
m_dRobertDuering = new wxStaticText( m_dScroll, wxID_ANY, wxT("Robert Duering"), wxDefaultPosition, wxDefaultSize, 0 );
m_dRobertDuering->Wrap( -1 );
m_dSizer->Add( m_dRobertDuering, 0, wxALL, 5 );
m_dJimDeitch = new wxStaticText( m_dScroll, wxID_ANY, wxT("Jim Deitch"), wxDefaultPosition, wxDefaultSize, 0 );
m_dJimDeitch->Wrap( -1 );
m_dSizer->Add( m_dJimDeitch, 0, wxALL, 5 );
m_dNooElec = new wxStaticText( m_dScroll, wxID_ANY, wxT("NooElec Inc. / nooelec.com"), wxDefaultPosition, wxDefaultSize, 0 );
m_dNooElec->Wrap( -1 );
m_dSizer->Add( m_dNooElec, 0, wxALL, 5 );
m_dDavidAhlgren = new wxStaticText( m_dScroll, wxID_ANY, wxT("David Ahlgren"), wxDefaultPosition, wxDefaultSize, 0 );
m_dDavidAhlgren->Wrap( -1 );
m_dSizer->Add( m_dDavidAhlgren, 0, wxALL, 5 );
m_dRonaldCook = new wxStaticText( m_dScroll, wxID_ANY, wxT("Ronald Cook"), wxDefaultPosition, wxDefaultSize, 0 );
m_dRonaldCook->Wrap( -1 );
m_dSizer->Add( m_dRonaldCook, 0, wxALL, 5 );
m_dEricPeterson = new wxStaticText( m_dScroll, wxID_ANY, wxT("Eric Peterson"), wxDefaultPosition, wxDefaultSize, 0 );
m_dEricPeterson->Wrap( -1 );
m_dSizer->Add( m_dEricPeterson, 0, wxALL, 5 );
m_dGeoDistributing = new wxStaticText( m_dScroll, wxID_ANY, wxT("Geo Distributing"), wxDefaultPosition, wxDefaultSize, 0 );
m_dGeoDistributing->Wrap( -1 );
m_dSizer->Add( m_dGeoDistributing, 0, wxALL, 5 );
m_dJamesCarson = new wxStaticText( m_dScroll, wxID_ANY, wxT("James Carson"), wxDefaultPosition, wxDefaultSize, 0 );
m_dJamesCarson->Wrap( -1 );
m_dSizer->Add( m_dJamesCarson, 0, wxALL, 5 );
m_dCraigWilliams = new wxStaticText( m_dScroll, wxID_ANY, wxT("Craig Williams"), wxDefaultPosition, wxDefaultSize, 0 );
m_dCraigWilliams->Wrap( -1 );
m_dSizer->Add( m_dCraigWilliams, 0, wxALL, 5 );
m_dRudolfShaffer = new wxStaticText( m_dScroll, wxID_ANY, wxT("Rudolf Schaffer"), wxDefaultPosition, wxDefaultSize, 0 );
m_dRudolfShaffer->Wrap( -1 );
m_dSizer->Add( m_dRudolfShaffer, 0, wxALL, 5 );
m_dJohnKaton = new wxStaticText( m_dScroll, wxID_ANY, wxT("John Katon"), wxDefaultPosition, wxDefaultSize, 0 );
m_dJohnKaton->Wrap( -1 );
m_dSizer->Add( m_dJohnKaton, 0, wxALL, 5 );
m_dVincentSonnier = new wxStaticText( m_dScroll, wxID_ANY, wxT("Vincent Sonnier"), wxDefaultPosition, wxDefaultSize, 0 );
m_dVincentSonnier->Wrap( -1 );
m_dSizer->Add( m_dVincentSonnier, 0, wxALL, 5 );
m_dCorq = new wxStaticText( m_dScroll, wxID_ANY, wxT("corq's auctions/L. Easterly LTD (x 5)"), wxDefaultPosition, wxDefaultSize, 0 );
m_dCorq->Wrap( -1 );
m_dSizer->Add( m_dCorq, 0, wxALL, 5 );
m_dIvanAlekseev = new wxStaticText( m_dScroll, wxID_ANY, wxT("Ivan Alekseev"), wxDefaultPosition, wxDefaultSize, 0 );
m_dIvanAlekseev->Wrap( -1 );
m_dSizer->Add( m_dIvanAlekseev, 0, wxALL, 5 );
m_dOleJorgenKolsrud = new wxStaticText( m_dScroll, wxID_ANY, wxT("Ole-Jørgen Næss Kolsrud"), wxDefaultPosition, wxDefaultSize, 0 );
m_dOleJorgenKolsrud->Wrap( -1 );
m_dSizer->Add( m_dOleJorgenKolsrud, 0, wxALL, 5 );
m_dHenrikJagemyr = new wxStaticText( m_dScroll, wxID_ANY, wxT("Henrik Jagemyr"), wxDefaultPosition, wxDefaultSize, 0 );
m_dHenrikJagemyr->Wrap( -1 );
m_dSizer->Add( m_dHenrikJagemyr, 0, wxALL, 5 );
m_dPeterHaines = new wxStaticText( m_dScroll, wxID_ANY, wxT("Peter Haines"), wxDefaultPosition, wxDefaultSize, 0 );
m_dPeterHaines->Wrap( -1 );
m_dSizer->Add( m_dPeterHaines, 0, wxALL, 5 );
m_dLeonAbrassart = new wxStaticText( m_dScroll, wxID_ANY, wxT("Leon Abrassart"), wxDefaultPosition, wxDefaultSize, 0 );
m_dLeonAbrassart->Wrap( -1 );
m_dSizer->Add( m_dLeonAbrassart, 0, wxALL, 5 );
m_dGeorgeTalbot = new wxStaticText( m_dScroll, wxID_ANY, wxT("George Alan Talbot"), wxDefaultPosition, wxDefaultSize, 0 );
m_dGeorgeTalbot->Wrap( -1 );
m_dSizer->Add( m_dGeorgeTalbot, 0, wxALL, 5 );
m_dFranciscoPuerta = new wxStaticText( m_dScroll, wxID_ANY, wxT("Francisco Borja Marcos de la Puerta"), wxDefaultPosition, wxDefaultSize, 0 );
m_dFranciscoPuerta->Wrap( -1 );
m_dSizer->Add( m_dFranciscoPuerta, 0, wxALL, 5 );
m_dRonaldLundeen = new wxStaticText( m_dScroll, wxID_ANY, wxT("Ronald A. Lundeen"), wxDefaultPosition, wxDefaultSize, 0 );
m_dRonaldLundeen->Wrap( -1 );
m_dSizer->Add( m_dRonaldLundeen, 0, wxALL, 5 );
m_dWalterHorbert = new wxStaticText( m_dScroll, wxID_ANY, wxT("Walter Horbert"), wxDefaultPosition, wxDefaultSize, 0 );
m_dWalterHorbert->Wrap( -1 );
m_dSizer->Add( m_dWalterHorbert, 0, wxALL, 5 );
m_dWilliamLD = new wxStaticText( m_dScroll, wxID_ANY, wxT("William Lloyd-Davies"), wxDefaultPosition, wxDefaultSize, 0 );
m_dWilliamLD->Wrap( -1 );
m_dSizer->Add( m_dWilliamLD, 0, wxALL, 5 );
m_dBratislavArandjelovic = new wxStaticText( m_dScroll, wxID_ANY, wxT("Bratislav Arandjelovic"), wxDefaultPosition, wxDefaultSize, 0 );
m_dBratislavArandjelovic->Wrap( -1 );
m_dSizer->Add( m_dBratislavArandjelovic, 0, wxALL, 5 );
m_dGaryMartin = new wxStaticText( m_dScroll, wxID_ANY, wxT("Gary Martin"), wxDefaultPosition, wxDefaultSize, 0 );
m_dGaryMartin->Wrap( -1 );
m_dSizer->Add( m_dGaryMartin, 0, wxALL, 5 );
m_dEinarsRepse = new wxStaticText( m_dScroll, wxID_ANY, wxT("Einars Repse"), wxDefaultPosition, wxDefaultSize, 0 );
m_dEinarsRepse->Wrap( -1 );
m_dSizer->Add( m_dEinarsRepse, 0, wxALL, 5 );
m_dTimothyGatton = new wxStaticText( m_dScroll, wxID_ANY, wxT("Timothy Gatton"), wxDefaultPosition, wxDefaultSize, 0 );
m_dTimothyGatton->Wrap( -1 );
m_dSizer->Add( m_dTimothyGatton, 0, wxALL, 5 );
m_dStephenCuccio = new wxStaticText( m_dScroll, wxID_ANY, wxT("Stephen Cuccio"), wxDefaultPosition, wxDefaultSize, 0 );
m_dStephenCuccio->Wrap( -1 );
m_dSizer->Add( m_dStephenCuccio, 0, wxALL, 5 );
m_dKeshavlalPatel = new wxStaticText( m_dScroll, wxID_ANY, wxT("Keshavlal Patel"), wxDefaultPosition, wxDefaultSize, 0 );
m_dKeshavlalPatel->Wrap( -1 );
m_dSizer->Add( m_dKeshavlalPatel, 0, wxALL, 5 );
m_dBobSchatzman = new wxStaticText( m_dScroll, wxID_ANY, wxT("Bob Schatzman"), wxDefaultPosition, wxDefaultSize, 0 );
m_dBobSchatzman->Wrap( -1 );
m_dSizer->Add( m_dBobSchatzman, 0, wxALL, 5 );
m_dRobertRoss = new wxStaticText( m_dScroll, wxID_ANY, wxT("Robert Ross"), wxDefaultPosition, wxDefaultSize, 0 );
m_dRobertRoss->Wrap( -1 );
m_dSizer->Add( m_dRobertRoss, 0, wxALL, 5 );
m_dRobertoBellotti = new wxStaticText( m_dScroll, wxID_ANY, wxT("Roberto Bellotti"), wxDefaultPosition, wxDefaultSize, 0 );
m_dRobertoBellotti->Wrap( -1 );
m_dSizer->Add( m_dRobertoBellotti, 0, wxALL, 5 );
m_dSergeVanderTorre = new wxStaticText( m_dScroll, wxID_ANY, wxT("Serge Van der Torre"), wxDefaultPosition, wxDefaultSize, 0 );
m_dSergeVanderTorre->Wrap( -1 );
m_dSizer->Add( m_dSergeVanderTorre, 0, wxALL, 5 );
m_dDieterSchneider = new wxStaticText( m_dScroll, wxID_ANY, wxT("Dieter Schneider"), wxDefaultPosition, wxDefaultSize, 0 );
m_dDieterSchneider->Wrap( -1 );
m_dSizer->Add( m_dDieterSchneider, 0, wxALL, 5 );
m_dPetrikaJaneku = new wxStaticText( m_dScroll, wxID_ANY, wxT("Petrika Janeku"), wxDefaultPosition, wxDefaultSize, 0 );
m_dPetrikaJaneku->Wrap( -1 );
m_dSizer->Add( m_dPetrikaJaneku, 0, wxALL, 5 );
m_dChadMyslinsky = new wxStaticText( m_dScroll, wxID_ANY, wxT("Chad Myslinsky"), wxDefaultPosition, wxDefaultSize, 0 );
m_dChadMyslinsky->Wrap( -1 );
m_dSizer->Add( m_dChadMyslinsky, 0, wxALL, 5 );
m_dCharlieBruckner = new wxStaticText( m_dScroll, wxID_ANY, wxT("Charlie Bruckner"), wxDefaultPosition, wxDefaultSize, 0 );
m_dCharlieBruckner->Wrap( -1 );
m_dSizer->Add( m_dCharlieBruckner, 0, wxALL, 5 );
m_dJordanParker = new wxStaticText( m_dScroll, wxID_ANY, wxT("Jordan Parker"), wxDefaultPosition, wxDefaultSize, 0 );
m_dJordanParker->Wrap( -1 );
m_dSizer->Add( m_dJordanParker, 0, wxALL, 5 );
m_dRobertChave = new wxStaticText( m_dScroll, wxID_ANY, wxT("Robert Chave Applied Physics Inc"), wxDefaultPosition, wxDefaultSize, 0 );
m_dRobertChave->Wrap( -1 );
m_dSizer->Add( m_dRobertChave, 0, wxALL, 5 );
m_dMarvinCalvert = new wxStaticText( m_dScroll, wxID_ANY, wxT("Marvin Calvert"), wxDefaultPosition, wxDefaultSize, 0 );
m_dMarvinCalvert->Wrap( -1 );
m_dSizer->Add( m_dMarvinCalvert, 0, wxALL, 5 );
m_dChrisStone = new wxStaticText( m_dScroll, wxID_ANY, wxT("Chris Stone"), wxDefaultPosition, wxDefaultSize, 0 );
m_dChrisStone->Wrap( -1 );
m_dSizer->Add( m_dChrisStone, 0, wxALL, 5 );
m_dErfurterFeurblume = new wxStaticText( m_dScroll, wxID_ANY, wxT("Erfurter Feurblume"), wxDefaultPosition, wxDefaultSize, 0 );
m_dErfurterFeurblume->Wrap( -1 );
m_dSizer->Add( m_dErfurterFeurblume, 0, wxALL, 5 );
m_dMakarenkoAleksey = new wxStaticText( m_dScroll, wxID_ANY, wxT("Makarenko Aleksey"), wxDefaultPosition, wxDefaultSize, 0 );
m_dMakarenkoAleksey->Wrap( -1 );
m_dSizer->Add( m_dMakarenkoAleksey, 0, wxALL, 5 );
m_dAnthonyLambiris = new wxStaticText( m_dScroll, wxID_ANY, wxT("Anthony Lambiris"), wxDefaultPosition, wxDefaultSize, 0 );
m_dAnthonyLambiris->Wrap( -1 );
m_dSizer->Add( m_dAnthonyLambiris, 0, wxALL, 5 );
m_dJoeBurtinsky = new wxStaticText( m_dScroll, wxID_ANY, wxT("Joe Burtinsky"), wxDefaultPosition, wxDefaultSize, 0 );
m_dJoeBurtinsky->Wrap( -1 );
m_dSizer->Add( m_dJoeBurtinsky, 0, wxALL, 5 );
m_dDalePuckett
= new wxStaticText( m_dScroll, wxID_ANY, wxT("Dale Puckett"), wxDefaultPosition, wxDefaultSize, 0 );
m_dDalePuckett
->Wrap( -1 );
m_dSizer->Add( m_dDalePuckett
, 0, wxALL, 5 );
m_dPatrickPreitner = new wxStaticText( m_dScroll, wxID_ANY, wxT("Patrick Preitner"), wxDefaultPosition, wxDefaultSize, 0 );
m_dPatrickPreitner->Wrap( -1 );
m_dSizer->Add( m_dPatrickPreitner, 0, wxALL, 5 );
m_dWilliamSoley = new wxStaticText( m_dScroll, wxID_ANY, wxT("William Soley"), wxDefaultPosition, wxDefaultSize, 0 );
m_dWilliamSoley->Wrap( -1 );
m_dSizer->Add( m_dWilliamSoley, 0, wxALL, 5 );
m_dPhilippRudin = new wxStaticText( m_dScroll, wxID_ANY, wxT("Philipp Rudin"), wxDefaultPosition, wxDefaultSize, 0 );
m_dPhilippRudin->Wrap( -1 );
m_dSizer->Add( m_dPhilippRudin, 0, wxALL, 5 );
m_dTerranceWilliams = new wxStaticText( m_dScroll, wxID_ANY, wxT("Terrance Williams"), wxDefaultPosition, wxDefaultSize, 0 );
m_dTerranceWilliams->Wrap( -1 );
m_dSizer->Add( m_dTerranceWilliams, 0, wxALL, 5 );
m_dCharlesSmith = new wxStaticText( m_dScroll, wxID_ANY, wxT("Charles Smith"), wxDefaultPosition, wxDefaultSize, 0 );
m_dCharlesSmith->Wrap( -1 );
m_dSizer->Add( m_dCharlesSmith, 0, wxALL, 5 );
m_dIanBrooks = new wxStaticText( m_dScroll, wxID_ANY, wxT("Ian Brooks"), wxDefaultPosition, wxDefaultSize, 0 );
m_dIanBrooks->Wrap( -1 );
m_dSizer->Add( m_dIanBrooks, 0, wxALL, 5 );
m_dIJorgBehrens = new wxStaticText( m_dScroll, wxID_ANY, wxT("Jörg Behrens"), wxDefaultPosition, wxDefaultSize, 0 );
m_dIJorgBehrens->Wrap( -1 );
m_dSizer->Add( m_dIJorgBehrens, 0, wxALL, 5 );
m_dDanielGarley = new wxStaticText( m_dScroll, wxID_ANY, wxT("Daniel Garley"), wxDefaultPosition, wxDefaultSize, 0 );
m_dDanielGarley->Wrap( -1 );
m_dSizer->Add( m_dDanielGarley, 0, wxALL, 5 );
m_dDavidWitten = new wxStaticText( m_dScroll, wxID_ANY, wxT("David Witten"), wxDefaultPosition, wxDefaultSize, 0 );
m_dDavidWitten->Wrap( -1 );
m_dSizer->Add( m_dDavidWitten, 0, wxALL, 5 );
m_dMartinLanda = new wxStaticText( m_dScroll, wxID_ANY, wxT("Martin Landa"), wxDefaultPosition, wxDefaultSize, 0 );
m_dMartinLanda->Wrap( -1 );
m_dSizer->Add( m_dMartinLanda, 0, wxALL, 5 );
m_dJosStark = new wxStaticText( m_dScroll, wxID_ANY, wxT("Jos Stark"), wxDefaultPosition, wxDefaultSize, 0 );
m_dJosStark->Wrap( -1 );
m_dSizer->Add( m_dJosStark, 0, wxALL, 5 );
m_dGeoffroyKoechlin = new wxStaticText( m_dScroll, wxID_ANY, wxT("Geoffroy Koechlin"), wxDefaultPosition, wxDefaultSize, 0 );
m_dGeoffroyKoechlin->Wrap( -1 );
m_dSizer->Add( m_dGeoffroyKoechlin, 0, wxALL, 5 );
m_dMichalPas = new wxStaticText( m_dScroll, wxID_ANY, wxT("Michal Pas"), wxDefaultPosition, wxDefaultSize, 0 );
m_dMichalPas->Wrap( -1 );
m_dSizer->Add( m_dMichalPas, 0, wxALL, 5 );
m_dWillEntriken = new wxStaticText( m_dScroll, wxID_ANY, wxT("Will Entriken"), wxDefaultPosition, wxDefaultSize, 0 );
m_dWillEntriken->Wrap( -1 );
m_dSizer->Add( m_dWillEntriken, 0, wxALL, 5 );
m_dManuelVerdeSalmeron = new wxStaticText( m_dScroll, wxID_ANY, wxT("Manuel Verde Salmeron"), wxDefaultPosition, wxDefaultSize, 0 );
m_dManuelVerdeSalmeron->Wrap( -1 );
m_dSizer->Add( m_dManuelVerdeSalmeron, 0, wxALL, 5 );
m_dJuhaPekkaHoglund = new wxStaticText( m_dScroll, wxID_ANY, wxT("Juha-Pekka Hoglund"), wxDefaultPosition, wxDefaultSize, 0 );
m_dJuhaPekkaHoglund->Wrap( -1 );
m_dSizer->Add( m_dJuhaPekkaHoglund, 0, wxALL, 5 );
m_dGoetzStroemsdoerfer = new wxStaticText( m_dScroll, wxID_ANY, wxT("Goetz Stroemsdoerfer"), wxDefaultPosition, wxDefaultSize, 0 );
m_dGoetzStroemsdoerfer->Wrap( -1 );
m_dSizer->Add( m_dGoetzStroemsdoerfer, 0, wxALL, 5 );
m_dJohnJBurgessJr = new wxStaticText( m_dScroll, wxID_ANY, wxT("John J Burgess Jr"), wxDefaultPosition, wxDefaultSize, 0 );
m_dJohnJBurgessJr->Wrap( -1 );
m_dSizer->Add( m_dJohnJBurgessJr, 0, wxALL, 5 );
m_dArturoCaballero = new wxStaticText( m_dScroll, wxID_ANY, wxT("Arturo Caballero"), wxDefaultPosition, wxDefaultSize, 0 );
m_dArturoCaballero->Wrap( -1 );
m_dSizer->Add( m_dArturoCaballero, 0, wxALL, 5 );
m_dRonaldTissier = new wxStaticText( m_dScroll, wxID_ANY, wxT("Ronald Tissier"), wxDefaultPosition, wxDefaultSize, 0 );
m_dRonaldTissier->Wrap( -1 );
m_dSizer->Add( m_dRonaldTissier, 0, wxALL, 5 );
m_dDavidLawrence = new wxStaticText( m_dScroll, wxID_ANY, wxT("David Lawrence"), wxDefaultPosition, wxDefaultSize, 0 );
m_dDavidLawrence->Wrap( -1 );
m_dSizer->Add( m_dDavidLawrence, 0, wxALL, 5 );
m_dScroll->SetSizer( m_dSizer );
m_dScroll->Layout();
m_dSizer->Fit( m_dScroll );
m_dScrollSizer->Add( m_dScroll, 1, wxEXPAND | wxALL, 5 );
m_dPanel->SetSizer( m_dScrollSizer );
m_dPanel->Layout();
m_dScrollSizer->Fit( m_dPanel );
m_aboutNotebook->AddPage( m_dPanel, wxT("Donations"), false );
m_stPanel = new wxPanel( m_aboutNotebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
wxBoxSizer* m_stScrollSizer;
m_stScrollSizer = new wxBoxSizer( wxVERTICAL );
m_stScroll = new wxScrolledWindow( m_stPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL );
m_stScroll->SetScrollRate( 5, 5 );
wxBoxSizer* m_stBSizer;
m_stBSizer = new wxBoxSizer( wxVERTICAL );
wxBoxSizer* m_stSizer;
m_stSizer = new wxBoxSizer( wxVERTICAL );
m_stHeader = new wxStaticText( m_stScroll, wxID_ANY, wxT("Special Thanks To"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE );
m_stHeader->Wrap( -1 );
m_stHeader->SetFont( wxFont( 15, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) );
m_stSizer->Add( m_stHeader, 0, wxALL, 5 );
m_stDivider1 = new wxStaticLine( m_stScroll, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
m_stSizer->Add( m_stDivider1, 0, wxEXPAND | wxALL, 5 );
m_stSoapyDevAssistHeader = new wxStaticText( m_stScroll, wxID_ANY, wxT("SoapySDR Development and Assistance:"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE );
m_stSoapyDevAssistHeader->Wrap( -1 );
m_stSoapyDevAssistHeader->SetFont( wxFont( 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) );
m_stSizer->Add( m_stSoapyDevAssistHeader, 0, wxALL, 5 );
m_stJoshBlum = new wxStaticText( m_stScroll, wxID_ANY, wxT("Josh Blum / @guruofquality / pothosware.com"), wxDefaultPosition, wxDefaultSize, 0 );
m_stJoshBlum->Wrap( -1 );
m_stSizer->Add( m_stJoshBlum, 0, wxALL, 5 );
m_stDivider2 = new wxStaticLine( m_stScroll, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
m_stSizer->Add( m_stDivider2, 0, wxEXPAND | wxALL, 5 );
m_stLiquidDSPHeader = new wxStaticText( m_stScroll, wxID_ANY, wxT("Liquid-DSP Development and Assistance:"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE );
m_stLiquidDSPHeader->Wrap( -1 );
m_stLiquidDSPHeader->SetFont( wxFont( 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) );
m_stSizer->Add( m_stLiquidDSPHeader, 0, wxALL, 5 );
m_stJosephGaeddert = new wxStaticText( m_stScroll, wxID_ANY, wxT("Joseph D. Gaeddert / @jgaeddert / liquidsdr.com"), wxDefaultPosition, wxDefaultSize, 0 );
m_stJosephGaeddert->Wrap( -1 );
m_stSizer->Add( m_stJosephGaeddert, 0, wxALL, 5 );
m_stDivider3 = new wxStaticLine( m_stScroll, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL );
m_stSizer->Add( m_stDivider3, 0, wxEXPAND | wxALL, 5 );
m_stIdeasDirectionsHeader = new wxStaticText( m_stScroll, wxID_ANY, wxT("Ideas, Direction && Encouragement:"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE );
m_stIdeasDirectionsHeader->Wrap( -1 );
m_stIdeasDirectionsHeader->SetFont( wxFont( 10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false, wxEmptyString ) );
m_stSizer->Add( m_stIdeasDirectionsHeader, 0, wxALL, 5 );
m_stTonMachielsen = new wxStaticText( m_stScroll, wxID_ANY, wxT("Ton Machielsen / @Toontje / @EA3HOE "), wxDefaultPosition, wxDefaultSize, 0 );
m_stTonMachielsen->Wrap( -1 );
m_stSizer->Add( m_stTonMachielsen, 0, wxALL, 5 );
m_stMikeLadd = new wxStaticText( m_stScroll, wxID_ANY, wxT("Mike Ladd / KD2KOG.com"), wxDefaultPosition, wxDefaultSize, 0 );
m_stMikeLadd->Wrap( -1 );
m_stSizer->Add( m_stMikeLadd, 0, wxALL, 5 );
m_stSDRplay = new wxStaticText( m_stScroll, wxID_ANY, wxT("SDRplay team / @SDRplay / SDRplay.com"), wxDefaultPosition, wxDefaultSize, 0 );
m_stSDRplay->Wrap( -1 );
m_stSizer->Add( m_stSDRplay, 0, wxALL, 5 );
m_stSDRplayFB = new wxStaticText( m_stScroll, wxID_ANY, wxT("SDRplay Facebook group"), wxDefaultPosition, wxDefaultSize, 0 );
m_stSDRplayFB->Wrap( -1 );
m_stSizer->Add( m_stSDRplayFB, 0, wxALL, 5 );
m_stPaulWarren = new wxStaticText( m_stScroll, wxID_ANY, wxT("Paul Warren / @pwarren"), wxDefaultPosition, wxDefaultSize, 0 );
m_stPaulWarren->Wrap( -1 );
m_stSizer->Add( m_stPaulWarren, 0, wxALL, 5 );
m_stSegesdiKaroly = new wxStaticText( m_stScroll, wxID_ANY, wxT("Segesdi Károly / @jazzkutya"), wxDefaultPosition, wxDefaultSize, 0 );
m_stSegesdiKaroly->Wrap( -1 );
m_stSizer->Add( m_stSegesdiKaroly, 0, wxALL, 5 );
m_stRedditRTLSDR = new wxStaticText( m_stScroll, wxID_ANY, wxT("Reddit RTL-SDR group /r/rtlsdr"), wxDefaultPosition, wxDefaultSize, 0 );
m_stRedditRTLSDR->Wrap( -1 );
m_stSizer->Add( m_stRedditRTLSDR, 0, wxALL, 5 );
m_stNooElec = new wxStaticText( m_stScroll, wxID_ANY, wxT("NooElec team / NooElec.com"), wxDefaultPosition, wxDefaultSize, 0 );
m_stNooElec->Wrap( -1 );
m_stSizer->Add( m_stNooElec, 0, wxALL, 5 );
m_stGHIssues = new wxStaticText( m_stScroll, wxID_ANY, wxT("Everyone who's contributed to the GitHub issues; thanks!"), wxDefaultPosition, wxDefaultSize, 0 );
m_stGHIssues->Wrap( -1 );
m_stSizer->Add( m_stGHIssues, 0, wxALL, 5 );
m_stNominate = new wxStaticText( m_stScroll, wxID_ANY, wxT("Please feel free to nominate anyone we might have missed."), wxDefaultPosition, wxDefaultSize, 0 );
m_stNominate->Wrap( -1 );
m_stSizer->Add( m_stNominate, 0, wxALL, 5 );
m_stBSizer->Add( m_stSizer, 1, wxALL|wxEXPAND, 5 );
m_stScroll->SetSizer( m_stBSizer );
m_stScroll->Layout();
m_stBSizer->Fit( m_stScroll );
m_stScrollSizer->Add( m_stScroll, 1, wxEXPAND | wxALL, 5 );
m_stPanel->SetSizer( m_stScrollSizer );
m_stPanel->Layout();
m_stScrollSizer->Fit( m_stPanel );
m_aboutNotebook->AddPage( m_stPanel, wxT("Special Thanks"), false );
dlgSizer->Add( m_aboutNotebook, 1, wxALL|wxEXPAND, 5 );
this->SetSizer( dlgSizer );
this->Layout();
this->Centre( wxBOTH );
}
AboutDialogBase::~AboutDialogBase()
{
}

View File

@ -0,0 +1,194 @@
///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version Oct 26 2018)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
///////////////////////////////////////////////////////////////////////////
#pragma once
#include <wx/artprov.h>
#include <wx/xrc/xmlres.h>
#include <wx/string.h>
#include <wx/stattext.h>
#include <wx/gdicmn.h>
#include <wx/font.h>
#include <wx/colour.h>
#include <wx/settings.h>
#include <wx/sizer.h>
#include <wx/panel.h>
#include <wx/statline.h>
#include <wx/scrolwin.h>
#include <wx/bitmap.h>
#include <wx/image.h>
#include <wx/icon.h>
#include <wx/notebook.h>
#include <wx/dialog.h>
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
/// Class AboutDialogBase
///////////////////////////////////////////////////////////////////////////////
class AboutDialogBase : public wxDialog
{
private:
protected:
wxPanel* m_hPanel;
wxStaticText* m_appName;
wxNotebook* m_aboutNotebook;
wxPanel* m_dbPanel;
wxScrolledWindow* m_dbScroll;
wxPanel* m_dbPanel1;
wxStaticText* m_dbHeader;
wxStaticText* m_dbGHHeader;
wxStaticText* m_dbTwitter;
wxStaticText* m_dbCharlesCliffe;
wxStaticText* m_dbghCC;
wxStaticText* m_dbtCC;
wxStaticText* m_dbVincentSonnier;
wxStaticText* m_dbghVS;
wxStaticText* m_dbtVS;
wxStaticLine* m_dbDivider1;
wxPanel* m_dbPanel2;
wxStaticText* m_cContributorsHeader;
wxStaticText* m_cGitHub;
wxStaticText* m_cCorneLukken;
wxStaticText* m_cghCL;
wxStaticText* m_cStainislawPitucha;
wxStaticText* m_cghSP;
wxStaticText* m_cTomSwartz;
wxStaticText* m_cghTS;
wxStaticText* m_cStefanTalpalaru;
wxStaticText* m_cghST;
wxStaticText* m_cDellRaySackett;
wxStaticText* m_cghDRS;
wxStaticText* m_cJiangWei;
wxStaticText* m_cghJW;
wxStaticText* m_cInfinityCyberworks;
wxStaticText* m_cghIC;
wxStaticText* m_cCrisMotch;
wxStaticText* m_cghCM;
wxStaticText* m_cAntiHax;
wxStaticText* m_cghAH;
wxStaticText* m_cRainbow;
wxStaticText* m_cghRBW;
wxStaticText* m_cMariuszRyndzionek;
wxStaticText* m_cghMR;
wxStaticText* m_cDrahosj;
wxStaticText* m_cghDra;
wxStaticText* m_cBenoitAllard;
wxStaticText* m_cghBA;
wxStaticText* m_cDianeBruce;
wxStaticText* m_cghDiB;
wxPanel* m_dPanel;
wxScrolledWindow* m_dScroll;
wxStaticText* m_dHeader;
wxStaticLine* m_dDivider1;
wxStaticText* m_dSDRplay;
wxStaticText* m_dMichaelLadd;
wxStaticText* m_dAutoMotiveTemplates;
wxStaticText* m_dJorgeMorales;
wxStaticText* m_dMichaelRooke;
wxStaticText* m_dTNCOM;
wxStaticText* m_dErikWied;
wxStaticText* m_dRobertDuering;
wxStaticText* m_dJimDeitch;
wxStaticText* m_dNooElec;
wxStaticText* m_dDavidAhlgren;
wxStaticText* m_dRonaldCook;
wxStaticText* m_dEricPeterson;
wxStaticText* m_dGeoDistributing;
wxStaticText* m_dJamesCarson;
wxStaticText* m_dCraigWilliams;
wxStaticText* m_dRudolfShaffer;
wxStaticText* m_dJohnKaton;
wxStaticText* m_dVincentSonnier;
wxStaticText* m_dCorq;
wxStaticText* m_dIvanAlekseev;
wxStaticText* m_dOleJorgenKolsrud;
wxStaticText* m_dHenrikJagemyr;
wxStaticText* m_dPeterHaines;
wxStaticText* m_dLeonAbrassart;
wxStaticText* m_dGeorgeTalbot;
wxStaticText* m_dFranciscoPuerta;
wxStaticText* m_dRonaldLundeen;
wxStaticText* m_dWalterHorbert;
wxStaticText* m_dWilliamLD;
wxStaticText* m_dBratislavArandjelovic;
wxStaticText* m_dGaryMartin;
wxStaticText* m_dEinarsRepse;
wxStaticText* m_dTimothyGatton;
wxStaticText* m_dStephenCuccio;
wxStaticText* m_dKeshavlalPatel;
wxStaticText* m_dBobSchatzman;
wxStaticText* m_dRobertRoss;
wxStaticText* m_dRobertoBellotti;
wxStaticText* m_dSergeVanderTorre;
wxStaticText* m_dDieterSchneider;
wxStaticText* m_dPetrikaJaneku;
wxStaticText* m_dChadMyslinsky;
wxStaticText* m_dCharlieBruckner;
wxStaticText* m_dJordanParker;
wxStaticText* m_dRobertChave;
wxStaticText* m_dMarvinCalvert;
wxStaticText* m_dChrisStone;
wxStaticText* m_dErfurterFeurblume;
wxStaticText* m_dMakarenkoAleksey;
wxStaticText* m_dAnthonyLambiris;
wxStaticText* m_dJoeBurtinsky;
wxStaticText* m_dDalePuckett
;
wxStaticText* m_dPatrickPreitner;
wxStaticText* m_dWilliamSoley;
wxStaticText* m_dPhilippRudin;
wxStaticText* m_dTerranceWilliams;
wxStaticText* m_dCharlesSmith;
wxStaticText* m_dIanBrooks;
wxStaticText* m_dIJorgBehrens;
wxStaticText* m_dDanielGarley;
wxStaticText* m_dDavidWitten;
wxStaticText* m_dMartinLanda;
wxStaticText* m_dJosStark;
wxStaticText* m_dGeoffroyKoechlin;
wxStaticText* m_dMichalPas;
wxStaticText* m_dWillEntriken;
wxStaticText* m_dManuelVerdeSalmeron;
wxStaticText* m_dJuhaPekkaHoglund;
wxStaticText* m_dGoetzStroemsdoerfer;
wxStaticText* m_dJohnJBurgessJr;
wxStaticText* m_dArturoCaballero;
wxStaticText* m_dRonaldTissier;
wxStaticText* m_dDavidLawrence;
wxPanel* m_stPanel;
wxScrolledWindow* m_stScroll;
wxStaticText* m_stHeader;
wxStaticLine* m_stDivider1;
wxStaticText* m_stSoapyDevAssistHeader;
wxStaticText* m_stJoshBlum;
wxStaticLine* m_stDivider2;
wxStaticText* m_stLiquidDSPHeader;
wxStaticText* m_stJosephGaeddert;
wxStaticLine* m_stDivider3;
wxStaticText* m_stIdeasDirectionsHeader;
wxStaticText* m_stTonMachielsen;
wxStaticText* m_stMikeLadd;
wxStaticText* m_stSDRplay;
wxStaticText* m_stSDRplayFB;
wxStaticText* m_stPaulWarren;
wxStaticText* m_stSegesdiKaroly;
wxStaticText* m_stRedditRTLSDR;
wxStaticText* m_stNooElec;
wxStaticText* m_stGHIssues;
wxStaticText* m_stNominate;
public:
AboutDialogBase( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("About"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 600,450 ), long style = wxDEFAULT_DIALOG_STYLE );
~AboutDialogBase();
};

View File

@ -0,0 +1,62 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "ActionDialog.h"
ActionDialog *ActionDialog::activeDialog = nullptr;
ActionDialog::ActionDialog( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style)
: ActionDialogBase(parent, id, title, pos, size, style) {
}
ActionDialog::~ActionDialog() = default;
void ActionDialog::showDialog(ActionDialog *dlg) {
if (activeDialog) { // rejected
delete dlg;
return;
}
activeDialog = dlg;
dlg->Layout();
dlg->Fit();
dlg->ShowModal();
}
ActionDialog *ActionDialog::getActiveDialog() {
return activeDialog;
}
void ActionDialog::setActiveDialog(ActionDialog *dlg) {
activeDialog = dlg;
}
void ActionDialog::onClickCancel( wxCommandEvent& /* event */ ) {
ActionDialog *dlg = activeDialog;
ActionDialog::setActiveDialog(nullptr);
dlg->EndModal(0);
doClickCancel();
delete dlg;
}
void ActionDialog::onClickOK( wxCommandEvent& /* event */ ) {
ActionDialog *dlg = activeDialog;
ActionDialog::setActiveDialog(nullptr);
dlg->EndModal(0);
doClickOK();
delete dlg;
}
void ActionDialog::doClickCancel() {
}
void ActionDialog::doClickOK() {
}

View File

@ -0,0 +1,24 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "ActionDialogBase.h"
#include <mutex>
class ActionDialog : public ActionDialogBase {
public:
explicit ActionDialog( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("QuestionTitle"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE );
~ActionDialog() override;
void onClickCancel( wxCommandEvent& event ) override;
void onClickOK( wxCommandEvent& event ) override;
virtual void doClickCancel();
virtual void doClickOK();
static ActionDialog *getActiveDialog();
static void setActiveDialog(ActionDialog *dlg);
static void showDialog(ActionDialog *dlg);
private:
static ActionDialog *activeDialog;
};

View File

@ -0,0 +1,53 @@
///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version Aug 8 2018)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
///////////////////////////////////////////////////////////////////////////
#include "ActionDialogBase.h"
///////////////////////////////////////////////////////////////////////////
ActionDialogBase::ActionDialogBase( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style )
{
this->SetSizeHints( wxDefaultSize, wxDefaultSize );
wxBoxSizer* mainSizer;
mainSizer = new wxBoxSizer( wxVERTICAL );
m_questionText = new wxStaticText( this, wxID_ANY, wxT("Question"), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER_HORIZONTAL );
m_questionText->Wrap( -1 );
mainSizer->Add( m_questionText, 1, wxALL|wxEXPAND, 5 );
wxBoxSizer* buttonSizer;
buttonSizer = new wxBoxSizer( wxHORIZONTAL );
m_cancelButton = new wxButton( this, wxID_ANY, wxT("Cancel"), wxDefaultPosition, wxDefaultSize, 0 );
buttonSizer->Add( m_cancelButton, 1, wxALL|wxEXPAND, 5 );
m_okButton = new wxButton( this, wxID_ANY, wxT("OK"), wxDefaultPosition, wxDefaultSize, 0 );
buttonSizer->Add( m_okButton, 1, wxALL|wxEXPAND, 5 );
mainSizer->Add( buttonSizer, 1, wxEXPAND, 5 );
this->SetSizer( mainSizer );
this->Layout();
mainSizer->Fit( this );
this->Centre( wxBOTH );
// Connect Events
m_cancelButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ActionDialogBase::onClickCancel ), NULL, this );
m_okButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ActionDialogBase::onClickOK ), NULL, this );
}
ActionDialogBase::~ActionDialogBase()
{
// Disconnect Events
m_cancelButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ActionDialogBase::onClickCancel ), NULL, this );
m_okButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ActionDialogBase::onClickOK ), NULL, this );
}

View File

@ -0,0 +1,421 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<wxFormBuilder_Project>
<FileVersion major="1" minor="14" />
<object class="Project" expanded="1">
<property name="class_decoration"></property>
<property name="code_generation">C++</property>
<property name="disconnect_events">1</property>
<property name="disconnect_mode">source_name</property>
<property name="disconnect_php_events">0</property>
<property name="disconnect_python_events">0</property>
<property name="embedded_files_path">res</property>
<property name="encoding">UTF-8</property>
<property name="event_generation">connect</property>
<property name="file">ActionDialogBase</property>
<property name="first_id">1000</property>
<property name="help_provider">none</property>
<property name="indent_with_spaces"></property>
<property name="internationalize">0</property>
<property name="name">ActionDialogBase</property>
<property name="namespace"></property>
<property name="path">.</property>
<property name="precompiled_header"></property>
<property name="relative_path">1</property>
<property name="skip_lua_events">1</property>
<property name="skip_php_events">1</property>
<property name="skip_python_events">1</property>
<property name="ui_table">UI</property>
<property name="use_enum">0</property>
<property name="use_microsoft_bom">0</property>
<object class="Dialog" expanded="1">
<property name="aui_managed">0</property>
<property name="aui_manager_style">wxAUI_MGR_DEFAULT</property>
<property name="bg"></property>
<property name="center">wxBOTH</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="enabled">1</property>
<property name="event_handler">impl_virtual</property>
<property name="extra_style"></property>
<property name="fg"></property>
<property name="font"></property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="maximum_size"></property>
<property name="minimum_size"></property>
<property name="name">ActionDialogBase</property>
<property name="pos"></property>
<property name="size"></property>
<property name="style">wxDEFAULT_DIALOG_STYLE</property>
<property name="subclass"></property>
<property name="title">QuestionTitle</property>
<property name="tooltip"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<event name="OnActivate"></event>
<event name="OnActivateApp"></event>
<event name="OnAuiPaneActivated"></event>
<event name="OnAuiPaneButton"></event>
<event name="OnAuiPaneClose"></event>
<event name="OnAuiPaneMaximize"></event>
<event name="OnAuiPaneRestore"></event>
<event name="OnAuiRender"></event>
<event name="OnAux1DClick"></event>
<event name="OnAux1Down"></event>
<event name="OnAux1Up"></event>
<event name="OnAux2DClick"></event>
<event name="OnAux2Down"></event>
<event name="OnAux2Up"></event>
<event name="OnChar"></event>
<event name="OnCharHook"></event>
<event name="OnClose"></event>
<event name="OnEnterWindow"></event>
<event name="OnEraseBackground"></event>
<event name="OnHibernate"></event>
<event name="OnIconize"></event>
<event name="OnIdle"></event>
<event name="OnInitDialog"></event>
<event name="OnKeyDown"></event>
<event name="OnKeyUp"></event>
<event name="OnKillFocus"></event>
<event name="OnLeaveWindow"></event>
<event name="OnLeftDClick"></event>
<event name="OnLeftDown"></event>
<event name="OnLeftUp"></event>
<event name="OnMaximize"></event>
<event name="OnMiddleDClick"></event>
<event name="OnMiddleDown"></event>
<event name="OnMiddleUp"></event>
<event name="OnMotion"></event>
<event name="OnMouseEvents"></event>
<event name="OnMouseWheel"></event>
<event name="OnMove"></event>
<event name="OnMoveEnd"></event>
<event name="OnMoveStart"></event>
<event name="OnMoving"></event>
<event name="OnPaint"></event>
<event name="OnRightDClick"></event>
<event name="OnRightDown"></event>
<event name="OnRightUp"></event>
<event name="OnSetFocus"></event>
<event name="OnShow"></event>
<event name="OnSize"></event>
<event name="OnUpdateUI"></event>
<object class="wxBoxSizer" expanded="1">
<property name="minimum_size"></property>
<property name="name">mainSizer</property>
<property name="orient">wxVERTICAL</property>
<property name="permission">none</property>
<object class="sizeritem" expanded="0">
<property name="border">5</property>
<property name="flag">wxALL|wxEXPAND</property>
<property name="proportion">1</property>
<object class="wxStaticText" expanded="0">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">Question</property>
<property name="markup">0</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_questionText</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style">wxALIGN_CENTER_HORIZONTAL</property>
<property name="subclass"></property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<property name="wrap">-1</property>
<event name="OnAux1DClick"></event>
<event name="OnAux1Down"></event>
<event name="OnAux1Up"></event>
<event name="OnAux2DClick"></event>
<event name="OnAux2Down"></event>
<event name="OnAux2Up"></event>
<event name="OnChar"></event>
<event name="OnCharHook"></event>
<event name="OnEnterWindow"></event>
<event name="OnEraseBackground"></event>
<event name="OnKeyDown"></event>
<event name="OnKeyUp"></event>
<event name="OnKillFocus"></event>
<event name="OnLeaveWindow"></event>
<event name="OnLeftDClick"></event>
<event name="OnLeftDown"></event>
<event name="OnLeftUp"></event>
<event name="OnMiddleDClick"></event>
<event name="OnMiddleDown"></event>
<event name="OnMiddleUp"></event>
<event name="OnMotion"></event>
<event name="OnMouseEvents"></event>
<event name="OnMouseWheel"></event>
<event name="OnPaint"></event>
<event name="OnRightDClick"></event>
<event name="OnRightDown"></event>
<event name="OnRightUp"></event>
<event name="OnSetFocus"></event>
<event name="OnSize"></event>
<event name="OnUpdateUI"></event>
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxEXPAND</property>
<property name="proportion">1</property>
<object class="wxBoxSizer" expanded="1">
<property name="minimum_size"></property>
<property name="name">buttonSizer</property>
<property name="orient">wxHORIZONTAL</property>
<property name="permission">none</property>
<object class="sizeritem" expanded="0">
<property name="border">5</property>
<property name="flag">wxALL|wxEXPAND</property>
<property name="proportion">1</property>
<object class="wxButton" expanded="0">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="bitmap"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="current"></property>
<property name="default">0</property>
<property name="default_pane">0</property>
<property name="disabled"></property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="focus"></property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">Cancel</property>
<property name="margins"></property>
<property name="markup">0</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_cancelButton</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="position"></property>
<property name="pressed"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style"></property>
<property name="subclass"></property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="validator_data_type"></property>
<property name="validator_style">wxFILTER_NONE</property>
<property name="validator_type">wxDefaultValidator</property>
<property name="validator_variable"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<event name="OnAux1DClick"></event>
<event name="OnAux1Down"></event>
<event name="OnAux1Up"></event>
<event name="OnAux2DClick"></event>
<event name="OnAux2Down"></event>
<event name="OnAux2Up"></event>
<event name="OnButtonClick">onClickCancel</event>
<event name="OnChar"></event>
<event name="OnCharHook"></event>
<event name="OnEnterWindow"></event>
<event name="OnEraseBackground"></event>
<event name="OnKeyDown"></event>
<event name="OnKeyUp"></event>
<event name="OnKillFocus"></event>
<event name="OnLeaveWindow"></event>
<event name="OnLeftDClick"></event>
<event name="OnLeftDown"></event>
<event name="OnLeftUp"></event>
<event name="OnMiddleDClick"></event>
<event name="OnMiddleDown"></event>
<event name="OnMiddleUp"></event>
<event name="OnMotion"></event>
<event name="OnMouseEvents"></event>
<event name="OnMouseWheel"></event>
<event name="OnPaint"></event>
<event name="OnRightDClick"></event>
<event name="OnRightDown"></event>
<event name="OnRightUp"></event>
<event name="OnSetFocus"></event>
<event name="OnSize"></event>
<event name="OnUpdateUI"></event>
</object>
</object>
<object class="sizeritem" expanded="0">
<property name="border">5</property>
<property name="flag">wxALL|wxEXPAND</property>
<property name="proportion">1</property>
<object class="wxButton" expanded="0">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="bitmap"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="current"></property>
<property name="default">0</property>
<property name="default_pane">0</property>
<property name="disabled"></property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="focus"></property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">OK</property>
<property name="margins"></property>
<property name="markup">0</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_okButton</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="position"></property>
<property name="pressed"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style"></property>
<property name="subclass"></property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="validator_data_type"></property>
<property name="validator_style">wxFILTER_NONE</property>
<property name="validator_type">wxDefaultValidator</property>
<property name="validator_variable"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<event name="OnAux1DClick"></event>
<event name="OnAux1Down"></event>
<event name="OnAux1Up"></event>
<event name="OnAux2DClick"></event>
<event name="OnAux2Down"></event>
<event name="OnAux2Up"></event>
<event name="OnButtonClick">onClickOK</event>
<event name="OnChar"></event>
<event name="OnCharHook"></event>
<event name="OnEnterWindow"></event>
<event name="OnEraseBackground"></event>
<event name="OnKeyDown"></event>
<event name="OnKeyUp"></event>
<event name="OnKillFocus"></event>
<event name="OnLeaveWindow"></event>
<event name="OnLeftDClick"></event>
<event name="OnLeftDown"></event>
<event name="OnLeftUp"></event>
<event name="OnMiddleDClick"></event>
<event name="OnMiddleDown"></event>
<event name="OnMiddleUp"></event>
<event name="OnMotion"></event>
<event name="OnMouseEvents"></event>
<event name="OnMouseWheel"></event>
<event name="OnPaint"></event>
<event name="OnRightDClick"></event>
<event name="OnRightDown"></event>
<event name="OnRightUp"></event>
<event name="OnSetFocus"></event>
<event name="OnSize"></event>
<event name="OnUpdateUI"></event>
</object>
</object>
</object>
</object>
</object>
</object>
</object>
</wxFormBuilder_Project>

View File

@ -0,0 +1,53 @@
///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version Aug 8 2018)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
///////////////////////////////////////////////////////////////////////////
#ifndef __ACTIONDIALOGBASE_H__
#define __ACTIONDIALOGBASE_H__
#include <wx/artprov.h>
#include <wx/xrc/xmlres.h>
#include <wx/string.h>
#include <wx/stattext.h>
#include <wx/gdicmn.h>
#include <wx/font.h>
#include <wx/colour.h>
#include <wx/settings.h>
#include <wx/bitmap.h>
#include <wx/image.h>
#include <wx/icon.h>
#include <wx/button.h>
#include <wx/sizer.h>
#include <wx/dialog.h>
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
/// Class ActionDialogBase
///////////////////////////////////////////////////////////////////////////////
class ActionDialogBase : public wxDialog
{
private:
protected:
wxStaticText* m_questionText;
wxButton* m_cancelButton;
wxButton* m_okButton;
// Virtual event handlers, override them in your derived class
virtual void onClickCancel( wxCommandEvent& event ) { event.Skip(); }
virtual void onClickOK( wxCommandEvent& event ) { event.Skip(); }
public:
ActionDialogBase( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("QuestionTitle"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxDEFAULT_DIALOG_STYLE );
~ActionDialogBase();
};
#endif //__ACTIONDIALOGBASE_H__

View File

@ -0,0 +1,51 @@
#include "PortSelectorDialog.h"
#include "rs232.h"
#include "CubicSDR.h"
PortSelectorDialog::PortSelectorDialog( wxWindow* parent, wxWindowID id, const std::string& defaultPort ) : PortSelectorDialogBase(parent, id) {
comEnumerate();
int nPorts = comGetNoPorts();
if (!defaultPort.empty()) {
m_portList->Append(defaultPort);
}
for (int i = 0; i < nPorts; i++) {
#ifdef WIN32
string portName(comGetPortName(i));
#else
string portName(comGetInternalName(i));
#endif
if (portName != defaultPort) {
m_portList->Append(portName);
}
}
comTerminate();
m_portSelection->SetValue(defaultPort);
}
void PortSelectorDialog::onListSelect( wxCommandEvent& /* event */ ) {
int pSelect = m_portList->GetSelection();
if (pSelect != -1) {
m_portSelection->SetValue(m_portList->GetString(pSelect));
}
}
void PortSelectorDialog::onCancelButton( wxCommandEvent& /* event */ ) {
wxGetApp().getAppFrame()->dismissRigControlPortDialog();
}
void PortSelectorDialog::onOKButton( wxCommandEvent& /* event */ ) {
wxGetApp().getAppFrame()->setRigControlPort(m_portSelection->GetValue().ToStdString());
}
void PortSelectorDialog::onClose(wxCloseEvent & /* event */) {
wxGetApp().getAppFrame()->dismissRigControlPortDialog();
}

View File

@ -0,0 +1,493 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<wxFormBuilder_Project>
<FileVersion major="1" minor="15" />
<object class="Project" expanded="1">
<property name="class_decoration"></property>
<property name="code_generation">C++</property>
<property name="disconnect_events">1</property>
<property name="disconnect_mode">source_name</property>
<property name="disconnect_php_events">0</property>
<property name="disconnect_python_events">0</property>
<property name="embedded_files_path">res</property>
<property name="encoding">UTF-8</property>
<property name="event_generation">connect</property>
<property name="file">PortSelectorDialogBase</property>
<property name="first_id">1000</property>
<property name="help_provider">none</property>
<property name="indent_with_spaces"></property>
<property name="internationalize">0</property>
<property name="name">PortSelectorDialog</property>
<property name="namespace"></property>
<property name="path">.</property>
<property name="precompiled_header"></property>
<property name="relative_path">1</property>
<property name="skip_lua_events">1</property>
<property name="skip_php_events">1</property>
<property name="skip_python_events">1</property>
<property name="ui_table">UI</property>
<property name="use_enum">0</property>
<property name="use_microsoft_bom">0</property>
<object class="Dialog" expanded="1">
<property name="aui_managed">0</property>
<property name="aui_manager_style">wxAUI_MGR_DEFAULT</property>
<property name="bg"></property>
<property name="center">wxBOTH</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="enabled">1</property>
<property name="event_handler">impl_virtual</property>
<property name="extra_style"></property>
<property name="fg"></property>
<property name="font"></property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="maximum_size"></property>
<property name="minimum_size">300,200</property>
<property name="name">PortSelectorDialogBase</property>
<property name="pos"></property>
<property name="size">320,260</property>
<property name="style">wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER</property>
<property name="subclass"></property>
<property name="title">Select Port</property>
<property name="tooltip"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<event name="OnClose">onClose</event>
<object class="wxBoxSizer" expanded="1">
<property name="minimum_size"></property>
<property name="name">dlgSizer</property>
<property name="orient">wxVERTICAL</property>
<property name="permission">none</property>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxALIGN_CENTER|wxTOP</property>
<property name="proportion">0</property>
<object class="wxStaticText" expanded="1">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">Select a detected port or enter your own</property>
<property name="markup">0</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_staticText1</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style"></property>
<property name="subclass"></property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<property name="wrap">-1</property>
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxALL|wxEXPAND</property>
<property name="proportion">1</property>
<object class="wxListBox" expanded="1">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="choices"></property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_portList</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style"></property>
<property name="subclass"></property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="validator_data_type"></property>
<property name="validator_style">wxFILTER_NONE</property>
<property name="validator_type">wxDefaultValidator</property>
<property name="validator_variable"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<event name="OnListBox">onListSelect</event>
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxEXPAND</property>
<property name="proportion">0</property>
<object class="wxBoxSizer" expanded="1">
<property name="minimum_size">-1,30</property>
<property name="name">bSizer3</property>
<property name="orient">wxHORIZONTAL</property>
<property name="permission">none</property>
<object class="sizeritem" expanded="0">
<property name="border">5</property>
<property name="flag">wxALIGN_CENTER_VERTICAL|wxALL</property>
<property name="proportion">0</property>
<object class="wxStaticText" expanded="0">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">Port</property>
<property name="markup">0</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_staticText2</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style"></property>
<property name="subclass"></property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<property name="wrap">-1</property>
</object>
</object>
<object class="sizeritem" expanded="0">
<property name="border">5</property>
<property name="flag">wxALIGN_CENTER_VERTICAL|wxALL</property>
<property name="proportion">1</property>
<object class="wxTextCtrl" expanded="0">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="maxlength"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_portSelection</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style"></property>
<property name="subclass"></property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="validator_data_type"></property>
<property name="validator_style">wxFILTER_NONE</property>
<property name="validator_type">wxDefaultValidator</property>
<property name="validator_variable"></property>
<property name="value"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
</object>
</object>
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxEXPAND</property>
<property name="proportion">0</property>
<object class="wxBoxSizer" expanded="1">
<property name="minimum_size"></property>
<property name="name">bSizer2</property>
<property name="orient">wxHORIZONTAL</property>
<property name="permission">none</property>
<object class="sizeritem" expanded="0">
<property name="border">5</property>
<property name="flag">wxALL|wxALIGN_BOTTOM</property>
<property name="proportion">0</property>
<object class="wxButton" expanded="0">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="bitmap"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="current"></property>
<property name="default">0</property>
<property name="default_pane">0</property>
<property name="disabled"></property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="focus"></property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">Cancel</property>
<property name="margins"></property>
<property name="markup">0</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_cancelButton</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="position"></property>
<property name="pressed"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style"></property>
<property name="subclass"></property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="validator_data_type"></property>
<property name="validator_style">wxFILTER_NONE</property>
<property name="validator_type">wxDefaultValidator</property>
<property name="validator_variable"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<event name="OnButtonClick">onCancelButton</event>
</object>
</object>
<object class="sizeritem" expanded="0">
<property name="border">5</property>
<property name="flag">wxEXPAND</property>
<property name="proportion">1</property>
<object class="spacer" expanded="0">
<property name="height">0</property>
<property name="permission">protected</property>
<property name="width">0</property>
</object>
</object>
<object class="sizeritem" expanded="0">
<property name="border">5</property>
<property name="flag">wxALL|wxALIGN_BOTTOM</property>
<property name="proportion">0</property>
<object class="wxButton" expanded="0">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="bitmap"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="current"></property>
<property name="default">0</property>
<property name="default_pane">0</property>
<property name="disabled"></property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="focus"></property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">OK</property>
<property name="margins"></property>
<property name="markup">0</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_okButton</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="position"></property>
<property name="pressed"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style"></property>
<property name="subclass"></property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="validator_data_type"></property>
<property name="validator_style">wxFILTER_NONE</property>
<property name="validator_type">wxDefaultValidator</property>
<property name="validator_variable"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<event name="OnButtonClick">onOKButton</event>
</object>
</object>
</object>
</object>
</object>
</object>
</object>
</wxFormBuilder_Project>

View File

@ -0,0 +1,12 @@
#include "PortSelectorDialogBase.h"
class PortSelectorDialog : public PortSelectorDialogBase {
public:
explicit PortSelectorDialog( wxWindow* parent, wxWindowID id = wxID_ANY, const std::string& defaultPort = "" );
protected:
void onListSelect( wxCommandEvent& event ) override;
void onCancelButton( wxCommandEvent& event ) override;
void onOKButton( wxCommandEvent& event ) override;
void onClose( wxCloseEvent& event ) override;
};

View File

@ -0,0 +1,76 @@
///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version Oct 26 2018)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
///////////////////////////////////////////////////////////////////////////
#include "PortSelectorDialogBase.h"
///////////////////////////////////////////////////////////////////////////
PortSelectorDialogBase::PortSelectorDialogBase( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style )
{
this->SetSizeHints( wxSize( 300,200 ), wxDefaultSize );
wxBoxSizer* dlgSizer;
dlgSizer = new wxBoxSizer( wxVERTICAL );
m_staticText1 = new wxStaticText( this, wxID_ANY, wxT("Select a detected port or enter your own"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticText1->Wrap( -1 );
dlgSizer->Add( m_staticText1, 0, wxALIGN_CENTER|wxTOP, 5 );
m_portList = new wxListBox( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, NULL, 0 );
dlgSizer->Add( m_portList, 1, wxALL|wxEXPAND, 5 );
wxBoxSizer* bSizer3;
bSizer3 = new wxBoxSizer( wxHORIZONTAL );
bSizer3->SetMinSize( wxSize( -1,30 ) );
m_staticText2 = new wxStaticText( this, wxID_ANY, wxT("Port"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticText2->Wrap( -1 );
bSizer3->Add( m_staticText2, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
m_portSelection = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
bSizer3->Add( m_portSelection, 1, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
dlgSizer->Add( bSizer3, 0, wxEXPAND, 5 );
wxBoxSizer* bSizer2;
bSizer2 = new wxBoxSizer( wxHORIZONTAL );
m_cancelButton = new wxButton( this, wxID_ANY, wxT("Cancel"), wxDefaultPosition, wxDefaultSize, 0 );
bSizer2->Add( m_cancelButton, 0, wxALL|wxALIGN_BOTTOM, 5 );
bSizer2->Add( 0, 0, 1, wxEXPAND, 5 );
m_okButton = new wxButton( this, wxID_ANY, wxT("OK"), wxDefaultPosition, wxDefaultSize, 0 );
bSizer2->Add( m_okButton, 0, wxALL|wxALIGN_BOTTOM, 5 );
dlgSizer->Add( bSizer2, 0, wxEXPAND, 5 );
this->SetSizer( dlgSizer );
this->Layout();
this->Centre( wxBOTH );
// Connect Events
this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( PortSelectorDialogBase::onClose ) );
m_portList->Connect( wxEVT_COMMAND_LISTBOX_SELECTED, wxCommandEventHandler( PortSelectorDialogBase::onListSelect ), NULL, this );
m_cancelButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PortSelectorDialogBase::onCancelButton ), NULL, this );
m_okButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PortSelectorDialogBase::onOKButton ), NULL, this );
}
PortSelectorDialogBase::~PortSelectorDialogBase()
{
// Disconnect Events
this->Disconnect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( PortSelectorDialogBase::onClose ) );
m_portList->Disconnect( wxEVT_COMMAND_LISTBOX_SELECTED, wxCommandEventHandler( PortSelectorDialogBase::onListSelect ), NULL, this );
m_cancelButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PortSelectorDialogBase::onCancelButton ), NULL, this );
m_okButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PortSelectorDialogBase::onOKButton ), NULL, this );
}

View File

@ -0,0 +1,58 @@
///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version Oct 26 2018)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
///////////////////////////////////////////////////////////////////////////
#pragma once
#include <wx/artprov.h>
#include <wx/xrc/xmlres.h>
#include <wx/string.h>
#include <wx/stattext.h>
#include <wx/gdicmn.h>
#include <wx/font.h>
#include <wx/colour.h>
#include <wx/settings.h>
#include <wx/listbox.h>
#include <wx/textctrl.h>
#include <wx/sizer.h>
#include <wx/bitmap.h>
#include <wx/image.h>
#include <wx/icon.h>
#include <wx/button.h>
#include <wx/dialog.h>
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
/// Class PortSelectorDialogBase
///////////////////////////////////////////////////////////////////////////////
class PortSelectorDialogBase : public wxDialog
{
private:
protected:
wxStaticText* m_staticText1;
wxListBox* m_portList;
wxStaticText* m_staticText2;
wxTextCtrl* m_portSelection;
wxButton* m_cancelButton;
wxButton* m_okButton;
// Virtual event handlers, override them in your derived class
virtual void onClose( wxCloseEvent& event ) { event.Skip(); }
virtual void onListSelect( wxCommandEvent& event ) { event.Skip(); }
virtual void onCancelButton( wxCommandEvent& event ) { event.Skip(); }
virtual void onOKButton( wxCommandEvent& event ) { event.Skip(); }
public:
PortSelectorDialogBase( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("Select Port"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 320,260 ), long style = wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER );
~PortSelectorDialogBase();
};

View File

@ -0,0 +1,139 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "DigitalConsole.h"
#include "CubicSDR.h"
#include <iomanip>
DigitalConsole::DigitalConsole( wxWindow* parent, ModemDigitalOutputConsole *doParent ): DigitalConsoleFrame( parent ), doParent(doParent) {
streamWritten.store(false);
streamPaused.store(false);
}
DigitalConsole::~DigitalConsole() {
doParent->setDialog(nullptr);
}
void DigitalConsole::OnClose( wxCloseEvent& /* event */ ) {
doParent->setDialog(nullptr);
}
void DigitalConsole::OnCopy( wxCommandEvent& /* event */ ) {
m_dataView->SelectAll();
m_dataView->Copy();
}
void DigitalConsole::OnPause( wxCommandEvent& /* event */ ) {
if (streamPaused.load()) {
m_pauseButton->SetLabel("Stop");
streamPaused.store(false);
} else {
m_pauseButton->SetLabel("Run");
streamPaused.store(true);
}
}
void DoRefresh( wxTimerEvent& event ) {
event.Skip();
}
void DigitalConsole::DoRefresh( wxTimerEvent& /* event */ ) {
if (streamWritten.load()) {
stream_busy.lock();
m_dataView->AppendText(streamBuf.str());
streamBuf.str("");
streamWritten.store(false);
stream_busy.unlock();
}
}
void DigitalConsole::OnClear( wxCommandEvent& /* event */ ) {
m_dataView->Clear();
}
void DigitalConsole::write(const std::string& outp) {
if (streamPaused.load()) {
return;
}
stream_busy.lock();
streamBuf << outp;
streamWritten.store(true);
stream_busy.unlock();
}
void DigitalConsole::write(char outc) {
if (streamPaused.load()) {
return;
}
stream_busy.lock();
streamBuf << outc;
streamWritten.store(true);
stream_busy.unlock();
}
ModemDigitalOutputConsole::ModemDigitalOutputConsole(): ModemDigitalOutput(), dialog(nullptr) {
streamWritten.store(false);
}
ModemDigitalOutputConsole::~ModemDigitalOutputConsole() = default;
void ModemDigitalOutputConsole::setDialog(DigitalConsole *dialog_in) {
dialog = dialog_in;
if (dialog && !dialogTitle.empty()) {
dialog->SetTitle(dialogTitle);
}
}
DigitalConsole *ModemDigitalOutputConsole::getDialog() {
return dialog;
}
void ModemDigitalOutputConsole::Show() {
if (!dialog) {
return;
}
if (!dialog->IsShown()) {
dialog->Show();
}
}
void ModemDigitalOutputConsole::Hide() {
if (!dialog) {
return;
}
if (dialog->IsShown()) {
dialog->Hide();
}
}
void ModemDigitalOutputConsole::Close() {
if (!dialog) {
return;
}
dialog->Hide();
dialog->Close();
dialog = nullptr;
}
void ModemDigitalOutputConsole::setTitle(const std::string& title) {
if (dialog) {
dialog->SetTitle(title);
}
dialogTitle = title;
}
void ModemDigitalOutputConsole::write(std::string outp) {
if (!dialog) {
return;
}
dialog->write(outp);
}
void ModemDigitalOutputConsole::write(char outc) {
if (!dialog) {
return;
}
dialog->write(outc);
}

View File

@ -0,0 +1,485 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<wxFormBuilder_Project>
<FileVersion major="1" minor="14" />
<object class="Project" expanded="1">
<property name="class_decoration" />
<property name="code_generation">C++</property>
<property name="disconnect_events">1</property>
<property name="disconnect_mode">source_name</property>
<property name="disconnect_php_events">0</property>
<property name="disconnect_python_events">0</property>
<property name="embedded_files_path">res</property>
<property name="encoding">UTF-8</property>
<property name="event_generation">connect</property>
<property name="file">DigitalConsoleFrame</property>
<property name="first_id">1000</property>
<property name="help_provider">none</property>
<property name="internationalize">0</property>
<property name="name">DigitalConsole</property>
<property name="namespace" />
<property name="path">.</property>
<property name="precompiled_header" />
<property name="relative_path">1</property>
<property name="skip_lua_events">1</property>
<property name="skip_php_events">1</property>
<property name="skip_python_events">1</property>
<property name="ui_table">UI</property>
<property name="use_enum">0</property>
<property name="use_microsoft_bom">0</property>
<object class="Frame" expanded="1">
<property name="aui_managed">0</property>
<property name="aui_manager_style">wxAUI_MGR_DEFAULT</property>
<property name="bg" />
<property name="center">wxBOTH</property>
<property name="context_help" />
<property name="context_menu">1</property>
<property name="enabled">1</property>
<property name="event_handler">impl_virtual</property>
<property name="extra_style" />
<property name="fg" />
<property name="font" />
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="maximum_size" />
<property name="minimum_size" />
<property name="name">DigitalConsoleFrame</property>
<property name="pos" />
<property name="size">441,394</property>
<property name="style">wxCAPTION|wxFRAME_FLOAT_ON_PARENT|wxMAXIMIZE|wxMAXIMIZE_BOX|wxMINIMIZE|wxMINIMIZE_BOX|wxRESIZE_BORDER</property>
<property name="subclass">; </property>
<property name="title">Digital Output</property>
<property name="tooltip" />
<property name="window_extra_style">wxWS_EX_PROCESS_UI_UPDATES</property>
<property name="window_name" />
<property name="window_style">wxFULL_REPAINT_ON_RESIZE|wxTAB_TRAVERSAL</property>
<property name="xrc_skip_sizer">1</property>
<event name="OnActivate" />
<event name="OnActivateApp" />
<event name="OnAuiFindManager" />
<event name="OnAuiPaneButton" />
<event name="OnAuiPaneClose" />
<event name="OnAuiPaneMaximize" />
<event name="OnAuiPaneRestore" />
<event name="OnAuiRender" />
<event name="OnChar" />
<event name="OnClose">OnClose</event>
<event name="OnEnterWindow" />
<event name="OnEraseBackground" />
<event name="OnHibernate" />
<event name="OnIconize" />
<event name="OnIdle" />
<event name="OnKeyDown" />
<event name="OnKeyUp" />
<event name="OnKillFocus" />
<event name="OnLeaveWindow" />
<event name="OnLeftDClick" />
<event name="OnLeftDown" />
<event name="OnLeftUp" />
<event name="OnMiddleDClick" />
<event name="OnMiddleDown" />
<event name="OnMiddleUp" />
<event name="OnMotion" />
<event name="OnMouseEvents" />
<event name="OnMouseWheel" />
<event name="OnPaint" />
<event name="OnRightDClick" />
<event name="OnRightDown" />
<event name="OnRightUp" />
<event name="OnSetFocus" />
<event name="OnSize" />
<event name="OnUpdateUI" />
<object class="wxBoxSizer" expanded="1">
<property name="minimum_size" />
<property name="name">mainSizer</property>
<property name="orient">wxVERTICAL</property>
<property name="permission">none</property>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxEXPAND</property>
<property name="proportion">1</property>
<object class="wxBoxSizer" expanded="0">
<property name="minimum_size" />
<property name="name">dataViewSizer</property>
<property name="orient">wxVERTICAL</property>
<property name="permission">none</property>
<object class="sizeritem" expanded="0">
<property name="border">5</property>
<property name="flag">wxEXPAND</property>
<property name="proportion">1</property>
<object class="wxTextCtrl" expanded="0">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer" />
<property name="aui_name" />
<property name="aui_position" />
<property name="aui_row" />
<property name="best_size" />
<property name="bg" />
<property name="caption" />
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help" />
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg" />
<property name="floatable">1</property>
<property name="font">,90,90,-1,76,0</property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="max_size" />
<property name="maximize_button">0</property>
<property name="maximum_size" />
<property name="maxlength" />
<property name="min_size" />
<property name="minimize_button">0</property>
<property name="minimum_size" />
<property name="moveable">1</property>
<property name="name">m_dataView</property>
<property name="pane_border">1</property>
<property name="pane_position" />
<property name="pane_size" />
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos" />
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size" />
<property name="style">wxTE_CHARWRAP|wxTE_MULTILINE|wxTE_NOHIDESEL|wxTE_READONLY|wxTE_WORDWRAP</property>
<property name="subclass" />
<property name="toolbar_pane">0</property>
<property name="tooltip" />
<property name="validator_data_type" />
<property name="validator_style">wxFILTER_NONE</property>
<property name="validator_type">wxDefaultValidator</property>
<property name="validator_variable" />
<property name="value" />
<property name="window_extra_style">wxWS_EX_PROCESS_UI_UPDATES</property>
<property name="window_name" />
<property name="window_style">wxALWAYS_SHOW_SB|wxFULL_REPAINT_ON_RESIZE|wxVSCROLL|wxBORDER_SIMPLE|wxBORDER_NONE</property>
<event name="OnChar" />
<event name="OnEnterWindow" />
<event name="OnEraseBackground" />
<event name="OnKeyDown" />
<event name="OnKeyUp" />
<event name="OnKillFocus" />
<event name="OnLeaveWindow" />
<event name="OnLeftDClick" />
<event name="OnLeftDown" />
<event name="OnLeftUp" />
<event name="OnMiddleDClick" />
<event name="OnMiddleDown" />
<event name="OnMiddleUp" />
<event name="OnMotion" />
<event name="OnMouseEvents" />
<event name="OnMouseWheel" />
<event name="OnPaint" />
<event name="OnRightDClick" />
<event name="OnRightDown" />
<event name="OnRightUp" />
<event name="OnSetFocus" />
<event name="OnSize" />
<event name="OnText" />
<event name="OnTextEnter" />
<event name="OnTextMaxLen" />
<event name="OnTextURL" />
<event name="OnUpdateUI" />
</object>
</object>
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxALL|wxEXPAND</property>
<property name="proportion">0</property>
<object class="wxBoxSizer" expanded="0">
<property name="minimum_size" />
<property name="name">buttonSizer</property>
<property name="orient">wxHORIZONTAL</property>
<property name="permission">none</property>
<object class="sizeritem" expanded="0">
<property name="border">5</property>
<property name="flag">wxEXPAND</property>
<property name="proportion">1</property>
<object class="wxButton" expanded="0">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer" />
<property name="aui_name" />
<property name="aui_position" />
<property name="aui_row" />
<property name="best_size" />
<property name="bg" />
<property name="caption" />
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help" />
<property name="context_menu">1</property>
<property name="default">0</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg" />
<property name="floatable">1</property>
<property name="font" />
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">Clear</property>
<property name="max_size" />
<property name="maximize_button">0</property>
<property name="maximum_size" />
<property name="min_size" />
<property name="minimize_button">0</property>
<property name="minimum_size" />
<property name="moveable">1</property>
<property name="name">m_clearButton</property>
<property name="pane_border">1</property>
<property name="pane_position" />
<property name="pane_size" />
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos" />
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size" />
<property name="style" />
<property name="subclass" />
<property name="toolbar_pane">0</property>
<property name="tooltip" />
<property name="validator_data_type" />
<property name="validator_style">wxFILTER_NONE</property>
<property name="validator_type">wxDefaultValidator</property>
<property name="validator_variable" />
<property name="window_extra_style" />
<property name="window_name" />
<property name="window_style" />
<event name="OnButtonClick">OnClear</event>
<event name="OnChar" />
<event name="OnEnterWindow" />
<event name="OnEraseBackground" />
<event name="OnKeyDown" />
<event name="OnKeyUp" />
<event name="OnKillFocus" />
<event name="OnLeaveWindow" />
<event name="OnLeftDClick" />
<event name="OnLeftDown" />
<event name="OnLeftUp" />
<event name="OnMiddleDClick" />
<event name="OnMiddleDown" />
<event name="OnMiddleUp" />
<event name="OnMotion" />
<event name="OnMouseEvents" />
<event name="OnMouseWheel" />
<event name="OnPaint" />
<event name="OnRightDClick" />
<event name="OnRightDown" />
<event name="OnRightUp" />
<event name="OnSetFocus" />
<event name="OnSize" />
<event name="OnUpdateUI" />
</object>
</object>
<object class="sizeritem" expanded="0">
<property name="border">5</property>
<property name="flag">wxEXPAND</property>
<property name="proportion">1</property>
<object class="wxButton" expanded="0">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer" />
<property name="aui_name" />
<property name="aui_position" />
<property name="aui_row" />
<property name="best_size" />
<property name="bg" />
<property name="caption" />
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help" />
<property name="context_menu">1</property>
<property name="default">0</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg" />
<property name="floatable">1</property>
<property name="font" />
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">Copy</property>
<property name="max_size" />
<property name="maximize_button">0</property>
<property name="maximum_size" />
<property name="min_size" />
<property name="minimize_button">0</property>
<property name="minimum_size" />
<property name="moveable">1</property>
<property name="name">m_copyButton</property>
<property name="pane_border">1</property>
<property name="pane_position" />
<property name="pane_size" />
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos" />
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size" />
<property name="style" />
<property name="subclass" />
<property name="toolbar_pane">0</property>
<property name="tooltip" />
<property name="validator_data_type" />
<property name="validator_style">wxFILTER_NONE</property>
<property name="validator_type">wxDefaultValidator</property>
<property name="validator_variable" />
<property name="window_extra_style" />
<property name="window_name" />
<property name="window_style" />
<event name="OnButtonClick">OnCopy</event>
<event name="OnChar" />
<event name="OnEnterWindow" />
<event name="OnEraseBackground" />
<event name="OnKeyDown" />
<event name="OnKeyUp" />
<event name="OnKillFocus" />
<event name="OnLeaveWindow" />
<event name="OnLeftDClick" />
<event name="OnLeftDown" />
<event name="OnLeftUp" />
<event name="OnMiddleDClick" />
<event name="OnMiddleDown" />
<event name="OnMiddleUp" />
<event name="OnMotion" />
<event name="OnMouseEvents" />
<event name="OnMouseWheel" />
<event name="OnPaint" />
<event name="OnRightDClick" />
<event name="OnRightDown" />
<event name="OnRightUp" />
<event name="OnSetFocus" />
<event name="OnSize" />
<event name="OnUpdateUI" />
</object>
</object>
<object class="sizeritem" expanded="0">
<property name="border">5</property>
<property name="flag">wxEXPAND</property>
<property name="proportion">1</property>
<object class="wxButton" expanded="0">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer" />
<property name="aui_name" />
<property name="aui_position" />
<property name="aui_row" />
<property name="best_size" />
<property name="bg" />
<property name="caption" />
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help" />
<property name="context_menu">1</property>
<property name="default">0</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg" />
<property name="floatable">1</property>
<property name="font" />
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">Stop</property>
<property name="max_size" />
<property name="maximize_button">0</property>
<property name="maximum_size" />
<property name="min_size" />
<property name="minimize_button">0</property>
<property name="minimum_size" />
<property name="moveable">1</property>
<property name="name">m_pauseButton</property>
<property name="pane_border">1</property>
<property name="pane_position" />
<property name="pane_size" />
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos" />
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size" />
<property name="style" />
<property name="subclass" />
<property name="toolbar_pane">0</property>
<property name="tooltip" />
<property name="validator_data_type" />
<property name="validator_style">wxFILTER_NONE</property>
<property name="validator_type">wxDefaultValidator</property>
<property name="validator_variable" />
<property name="window_extra_style" />
<property name="window_name" />
<property name="window_style" />
<event name="OnButtonClick">OnPause</event>
<event name="OnChar" />
<event name="OnEnterWindow" />
<event name="OnEraseBackground" />
<event name="OnKeyDown" />
<event name="OnKeyUp" />
<event name="OnKillFocus" />
<event name="OnLeaveWindow" />
<event name="OnLeftDClick" />
<event name="OnLeftDown" />
<event name="OnLeftUp" />
<event name="OnMiddleDClick" />
<event name="OnMiddleDown" />
<event name="OnMiddleUp" />
<event name="OnMotion" />
<event name="OnMouseEvents" />
<event name="OnMouseWheel" />
<event name="OnPaint" />
<event name="OnRightDClick" />
<event name="OnRightDown" />
<event name="OnRightUp" />
<event name="OnSetFocus" />
<event name="OnSize" />
<event name="OnUpdateUI" />
</object>
</object>
</object>
</object>
</object>
<object class="wxTimer" expanded="1">
<property name="enabled">1</property>
<property name="id">wxID_ANY</property>
<property name="name">m_refreshTimer</property>
<property name="oneshot">0</property>
<property name="period">250</property>
<property name="permission">protected</property>
<event name="OnTimer">DoRefresh</event>
</object>
</object>
</object>
</wxFormBuilder_Project>

View File

@ -0,0 +1,64 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include <map>
#include <vector>
#include <sstream>
#include <ostream>
#include <mutex>
#include "DigitalConsoleFrame.h"
#include "ModemDigital.h"
class ModemDigitalOutputConsole;
class DigitalConsole: public DigitalConsoleFrame {
public:
DigitalConsole( wxWindow* parent, ModemDigitalOutputConsole *doParent );
~DigitalConsole() override;
void write(const std::string& outp);
void write(char outc);
private:
void DoRefresh( wxTimerEvent& event ) override;
void OnClose( wxCloseEvent& event ) override;
void OnClear( wxCommandEvent& event ) override;
void OnCopy( wxCommandEvent& event ) override;
void OnPause( wxCommandEvent& event ) override;
std::stringstream streamBuf;
std::mutex stream_busy;
std::atomic<bool> streamWritten;
std::atomic<bool> streamPaused;
ModemDigitalOutputConsole *doParent;
};
class ModemDigitalOutputConsole: public ModemDigitalOutput {
public:
ModemDigitalOutputConsole();
~ModemDigitalOutputConsole() override;
void setDialog(DigitalConsole *dialog_in);
DigitalConsole *getDialog();
void setTitle(const std::string& title);
void write(std::string outp) override;
void write(char outc) override;
void Show() override;
void Hide() override;
void Close() override;
private:
DigitalConsole *dialog;
std::stringstream streamBuf;
std::mutex stream_busy;
std::atomic<bool> streamWritten;
std::string dialogTitle;
};

View File

@ -0,0 +1,73 @@
///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version Aug 8 2018)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
///////////////////////////////////////////////////////////////////////////
#include "DigitalConsoleFrame.h"
///////////////////////////////////////////////////////////////////////////
DigitalConsoleFrame::DigitalConsoleFrame( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxFrame( parent, id, title, pos, size, style )
{
this->SetSizeHints( wxDefaultSize, wxDefaultSize );
this->SetExtraStyle( wxWS_EX_PROCESS_UI_UPDATES );
wxBoxSizer* mainSizer;
mainSizer = new wxBoxSizer( wxVERTICAL );
wxBoxSizer* dataViewSizer;
dataViewSizer = new wxBoxSizer( wxVERTICAL );
m_dataView = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_CHARWRAP|wxTE_MULTILINE|wxTE_NOHIDESEL|wxTE_READONLY|wxTE_WORDWRAP|wxALWAYS_SHOW_SB|wxFULL_REPAINT_ON_RESIZE|wxVSCROLL|wxBORDER_SIMPLE|wxBORDER_NONE );
m_dataView->SetExtraStyle( wxWS_EX_PROCESS_UI_UPDATES );
m_dataView->SetFont( wxFont( wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL, false, wxEmptyString ) );
dataViewSizer->Add( m_dataView, 1, wxEXPAND, 5 );
mainSizer->Add( dataViewSizer, 1, wxEXPAND, 5 );
wxBoxSizer* buttonSizer;
buttonSizer = new wxBoxSizer( wxHORIZONTAL );
m_clearButton = new wxButton( this, wxID_ANY, wxT("Clear"), wxDefaultPosition, wxDefaultSize, 0 );
buttonSizer->Add( m_clearButton, 1, wxEXPAND, 5 );
m_copyButton = new wxButton( this, wxID_ANY, wxT("Copy"), wxDefaultPosition, wxDefaultSize, 0 );
buttonSizer->Add( m_copyButton, 1, wxEXPAND, 5 );
m_pauseButton = new wxButton( this, wxID_ANY, wxT("Stop"), wxDefaultPosition, wxDefaultSize, 0 );
buttonSizer->Add( m_pauseButton, 1, wxEXPAND, 5 );
mainSizer->Add( buttonSizer, 0, wxALL|wxEXPAND, 5 );
this->SetSizer( mainSizer );
this->Layout();
m_refreshTimer.SetOwner( this, wxID_ANY );
m_refreshTimer.Start( 250 );
this->Centre( wxBOTH );
// Connect Events
this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( DigitalConsoleFrame::OnClose ) );
m_clearButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DigitalConsoleFrame::OnClear ), NULL, this );
m_copyButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DigitalConsoleFrame::OnCopy ), NULL, this );
m_pauseButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DigitalConsoleFrame::OnPause ), NULL, this );
this->Connect( wxID_ANY, wxEVT_TIMER, wxTimerEventHandler( DigitalConsoleFrame::DoRefresh ) );
}
DigitalConsoleFrame::~DigitalConsoleFrame()
{
// Disconnect Events
this->Disconnect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( DigitalConsoleFrame::OnClose ) );
m_clearButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DigitalConsoleFrame::OnClear ), NULL, this );
m_copyButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DigitalConsoleFrame::OnCopy ), NULL, this );
m_pauseButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DigitalConsoleFrame::OnPause ), NULL, this );
this->Disconnect( wxID_ANY, wxEVT_TIMER, wxTimerEventHandler( DigitalConsoleFrame::DoRefresh ) );
}

View File

@ -0,0 +1,60 @@
///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version Aug 8 2018)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
///////////////////////////////////////////////////////////////////////////
#ifndef __DIGITALCONSOLEFRAME_H__
#define __DIGITALCONSOLEFRAME_H__
#include <wx/artprov.h>
#include <wx/xrc/xmlres.h>
#include <wx/string.h>
#include <wx/textctrl.h>
#include <wx/gdicmn.h>
#include <wx/font.h>
#include <wx/colour.h>
#include <wx/settings.h>
#include <wx/sizer.h>
#include <wx/bitmap.h>
#include <wx/image.h>
#include <wx/icon.h>
#include <wx/button.h>
#include <wx/timer.h>
#include <wx/frame.h>
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
/// Class DigitalConsoleFrame
///////////////////////////////////////////////////////////////////////////////
class DigitalConsoleFrame : public wxFrame
{
private:
protected:
wxTextCtrl* m_dataView;
wxButton* m_clearButton;
wxButton* m_copyButton;
wxButton* m_pauseButton;
wxTimer m_refreshTimer;
// Virtual event handlers, override them in your derived class
virtual void OnClose( wxCloseEvent& event ) { event.Skip(); }
virtual void OnClear( wxCommandEvent& event ) { event.Skip(); }
virtual void OnCopy( wxCommandEvent& event ) { event.Skip(); }
virtual void OnPause( wxCommandEvent& event ) { event.Skip(); }
virtual void DoRefresh( wxTimerEvent& event ) { event.Skip(); }
public:
DigitalConsoleFrame( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("Digital Output"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 441,394 ), long style = wxCAPTION|wxFRAME_FLOAT_ON_PARENT|wxMAXIMIZE|wxMAXIMIZE_BOX|wxMINIMIZE|wxMINIMIZE_BOX|wxRESIZE_BORDER|wxFULL_REPAINT_ON_RESIZE|wxTAB_TRAVERSAL );
~DigitalConsoleFrame();
};
#endif //__DIGITALCONSOLEFRAME_H__

View File

@ -0,0 +1,61 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "SDRDeviceAdd.h"
#include "SDREnumerator.h"
SDRDeviceAddDialog::SDRDeviceAddDialog( wxWindow* parent ): SDRDeviceAddForm( parent ) {
okPressed = false;
selectedModule = "";
moduleParam = "";
selectedModule = "SoapyRemote";
m_soapyModule->Append("SoapyRemote");
m_paramLabel->SetLabel("Remote Address (address[:port])");
std::vector<std::string> &factories = SDREnumerator::getFactories();
std::vector<std::string>::iterator factory_i;
for (factory_i = factories.begin(); factory_i != factories.end(); factory_i++) {
if (*factory_i != "remote" && *factory_i != "null") {
m_soapyModule->Append(*factory_i);
}
}
}
void SDRDeviceAddDialog::OnSoapyModuleChanged( wxCommandEvent& /* event */) {
wxString strSel = m_soapyModule->GetStringSelection();
selectedModule = strSel.ToStdString();
if (selectedModule == "SoapyRemote") {
m_paramLabel->SetLabelText("Remote Address (address[:port])");
} else {
m_paramLabel->SetLabel("SoapySDR Device Parameters, i.e. 'addr=192.168.1.105'");
}
}
void SDRDeviceAddDialog::OnCancelButton( wxCommandEvent& /* event */) {
okPressed = false;
Close(true);
}
void SDRDeviceAddDialog::OnOkButton( wxCommandEvent& /* event */) {
wxString strSel = m_soapyModule->GetStringSelection();
selectedModule = strSel.ToStdString();
moduleParam = m_paramText->GetValue().ToStdString();
okPressed = true;
Close(true);
}
bool SDRDeviceAddDialog::wasOkPressed() const {
return okPressed;
}
std::string SDRDeviceAddDialog::getSelectedModule() {
return selectedModule;
}
std::string SDRDeviceAddDialog::getModuleParam() {
return moduleParam;
}

View File

@ -0,0 +1,681 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<wxFormBuilder_Project>
<FileVersion major="1" minor="14" />
<object class="Project" expanded="1">
<property name="class_decoration" />
<property name="code_generation">C++</property>
<property name="disconnect_events">1</property>
<property name="disconnect_mode">source_name</property>
<property name="disconnect_php_events">0</property>
<property name="disconnect_python_events">0</property>
<property name="embedded_files_path">res</property>
<property name="encoding">UTF-8</property>
<property name="event_generation">connect</property>
<property name="file">SDRDeviceAddForm</property>
<property name="first_id">1000</property>
<property name="help_provider">none</property>
<property name="internationalize">0</property>
<property name="name">SDRDeviceAddForm</property>
<property name="namespace" />
<property name="path">.</property>
<property name="precompiled_header" />
<property name="relative_path">1</property>
<property name="skip_lua_events">1</property>
<property name="skip_php_events">1</property>
<property name="skip_python_events">1</property>
<property name="ui_table">UI</property>
<property name="use_enum">0</property>
<property name="use_microsoft_bom">0</property>
<object class="Dialog" expanded="1">
<property name="aui_managed">0</property>
<property name="aui_manager_style">wxAUI_MGR_DEFAULT</property>
<property name="bg" />
<property name="center">wxBOTH</property>
<property name="context_help" />
<property name="context_menu">1</property>
<property name="enabled">1</property>
<property name="event_handler">impl_virtual</property>
<property name="extra_style" />
<property name="fg" />
<property name="font" />
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="maximum_size" />
<property name="minimum_size" />
<property name="name">SDRDeviceAddForm</property>
<property name="pos" />
<property name="size">395,293</property>
<property name="style">wxDEFAULT_DIALOG_STYLE</property>
<property name="subclass" />
<property name="title">Add SoapySDR Device</property>
<property name="tooltip" />
<property name="window_extra_style" />
<property name="window_name" />
<property name="window_style" />
<event name="OnActivate" />
<event name="OnActivateApp" />
<event name="OnAuiFindManager" />
<event name="OnAuiPaneButton" />
<event name="OnAuiPaneClose" />
<event name="OnAuiPaneMaximize" />
<event name="OnAuiPaneRestore" />
<event name="OnAuiRender" />
<event name="OnChar" />
<event name="OnClose" />
<event name="OnEnterWindow" />
<event name="OnEraseBackground" />
<event name="OnHibernate" />
<event name="OnIconize" />
<event name="OnIdle" />
<event name="OnInitDialog" />
<event name="OnKeyDown" />
<event name="OnKeyUp" />
<event name="OnKillFocus" />
<event name="OnLeaveWindow" />
<event name="OnLeftDClick" />
<event name="OnLeftDown" />
<event name="OnLeftUp" />
<event name="OnMiddleDClick" />
<event name="OnMiddleDown" />
<event name="OnMiddleUp" />
<event name="OnMotion" />
<event name="OnMouseEvents" />
<event name="OnMouseWheel" />
<event name="OnPaint" />
<event name="OnRightDClick" />
<event name="OnRightDown" />
<event name="OnRightUp" />
<event name="OnSetFocus" />
<event name="OnSize" />
<event name="OnUpdateUI" />
<object class="wxBoxSizer" expanded="1">
<property name="minimum_size" />
<property name="name">bSizer6</property>
<property name="orient">wxVERTICAL</property>
<property name="permission">none</property>
<object class="sizeritem" expanded="1">
<property name="border">8</property>
<property name="flag">wxALL</property>
<property name="proportion">0</property>
<object class="wxStaticText" expanded="1">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer" />
<property name="aui_name" />
<property name="aui_position" />
<property name="aui_row" />
<property name="best_size" />
<property name="bg" />
<property name="caption" />
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help" />
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg" />
<property name="floatable">1</property>
<property name="font" />
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">Manually add a SoapyRemote or SoapySDR device. &#x0A;&#x0A;Useful for a device that is not detected automatically.</property>
<property name="max_size" />
<property name="maximize_button">0</property>
<property name="maximum_size" />
<property name="min_size" />
<property name="minimize_button">0</property>
<property name="minimum_size" />
<property name="moveable">1</property>
<property name="name">m_staticText4</property>
<property name="pane_border">1</property>
<property name="pane_position" />
<property name="pane_size" />
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos" />
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size" />
<property name="style" />
<property name="subclass" />
<property name="toolbar_pane">0</property>
<property name="tooltip" />
<property name="window_extra_style" />
<property name="window_name" />
<property name="window_style" />
<property name="wrap">-1</property>
<event name="OnChar" />
<event name="OnEnterWindow" />
<event name="OnEraseBackground" />
<event name="OnKeyDown" />
<event name="OnKeyUp" />
<event name="OnKillFocus" />
<event name="OnLeaveWindow" />
<event name="OnLeftDClick" />
<event name="OnLeftDown" />
<event name="OnLeftUp" />
<event name="OnMiddleDClick" />
<event name="OnMiddleDown" />
<event name="OnMiddleUp" />
<event name="OnMotion" />
<event name="OnMouseEvents" />
<event name="OnMouseWheel" />
<event name="OnPaint" />
<event name="OnRightDClick" />
<event name="OnRightDown" />
<event name="OnRightUp" />
<event name="OnSetFocus" />
<event name="OnSize" />
<event name="OnUpdateUI" />
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxEXPAND</property>
<property name="proportion">1</property>
<object class="spacer" expanded="1">
<property name="height">0</property>
<property name="permission">protected</property>
<property name="width">0</property>
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">8</property>
<property name="flag">wxALL</property>
<property name="proportion">0</property>
<object class="wxChoice" expanded="1">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer" />
<property name="aui_name" />
<property name="aui_position" />
<property name="aui_row" />
<property name="best_size" />
<property name="bg" />
<property name="caption" />
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="choices" />
<property name="close_button">1</property>
<property name="context_help" />
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg" />
<property name="floatable">1</property>
<property name="font" />
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="max_size" />
<property name="maximize_button">0</property>
<property name="maximum_size" />
<property name="min_size" />
<property name="minimize_button">0</property>
<property name="minimum_size" />
<property name="moveable">1</property>
<property name="name">m_soapyModule</property>
<property name="pane_border">1</property>
<property name="pane_position" />
<property name="pane_size" />
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos" />
<property name="resize">Resizable</property>
<property name="selection">0</property>
<property name="show">1</property>
<property name="size" />
<property name="style" />
<property name="subclass" />
<property name="toolbar_pane">0</property>
<property name="tooltip" />
<property name="validator_data_type" />
<property name="validator_style">wxFILTER_NONE</property>
<property name="validator_type">wxDefaultValidator</property>
<property name="validator_variable" />
<property name="window_extra_style" />
<property name="window_name" />
<property name="window_style" />
<event name="OnChar" />
<event name="OnChoice">OnSoapyModuleChanged</event>
<event name="OnEnterWindow" />
<event name="OnEraseBackground" />
<event name="OnKeyDown" />
<event name="OnKeyUp" />
<event name="OnKillFocus" />
<event name="OnLeaveWindow" />
<event name="OnLeftDClick" />
<event name="OnLeftDown" />
<event name="OnLeftUp" />
<event name="OnMiddleDClick" />
<event name="OnMiddleDown" />
<event name="OnMiddleUp" />
<event name="OnMotion" />
<event name="OnMouseEvents" />
<event name="OnMouseWheel" />
<event name="OnPaint" />
<event name="OnRightDClick" />
<event name="OnRightDown" />
<event name="OnRightUp" />
<event name="OnSetFocus" />
<event name="OnSize" />
<event name="OnUpdateUI" />
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxEXPAND</property>
<property name="proportion">1</property>
<object class="spacer" expanded="1">
<property name="height">0</property>
<property name="permission">protected</property>
<property name="width">0</property>
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">8</property>
<property name="flag">wxALL</property>
<property name="proportion">0</property>
<object class="wxStaticText" expanded="1">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer" />
<property name="aui_name" />
<property name="aui_position" />
<property name="aui_row" />
<property name="best_size" />
<property name="bg" />
<property name="caption" />
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help" />
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg" />
<property name="floatable">1</property>
<property name="font" />
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">&lt;Parameter&gt;</property>
<property name="max_size" />
<property name="maximize_button">0</property>
<property name="maximum_size" />
<property name="min_size" />
<property name="minimize_button">0</property>
<property name="minimum_size" />
<property name="moveable">1</property>
<property name="name">m_paramLabel</property>
<property name="pane_border">1</property>
<property name="pane_position" />
<property name="pane_size" />
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos" />
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size" />
<property name="style" />
<property name="subclass" />
<property name="toolbar_pane">0</property>
<property name="tooltip" />
<property name="window_extra_style" />
<property name="window_name" />
<property name="window_style" />
<property name="wrap">-1</property>
<event name="OnChar" />
<event name="OnEnterWindow" />
<event name="OnEraseBackground" />
<event name="OnKeyDown" />
<event name="OnKeyUp" />
<event name="OnKillFocus" />
<event name="OnLeaveWindow" />
<event name="OnLeftDClick" />
<event name="OnLeftDown" />
<event name="OnLeftUp" />
<event name="OnMiddleDClick" />
<event name="OnMiddleDown" />
<event name="OnMiddleUp" />
<event name="OnMotion" />
<event name="OnMouseEvents" />
<event name="OnMouseWheel" />
<event name="OnPaint" />
<event name="OnRightDClick" />
<event name="OnRightDown" />
<event name="OnRightUp" />
<event name="OnSetFocus" />
<event name="OnSize" />
<event name="OnUpdateUI" />
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">8</property>
<property name="flag">wxALL|wxEXPAND</property>
<property name="proportion">1</property>
<object class="wxTextCtrl" expanded="1">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer" />
<property name="aui_name" />
<property name="aui_position" />
<property name="aui_row" />
<property name="best_size" />
<property name="bg" />
<property name="caption" />
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help" />
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg" />
<property name="floatable">1</property>
<property name="font" />
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="max_size" />
<property name="maximize_button">0</property>
<property name="maximum_size" />
<property name="maxlength" />
<property name="min_size">-1,-1</property>
<property name="minimize_button">0</property>
<property name="minimum_size">-1,48</property>
<property name="moveable">1</property>
<property name="name">m_paramText</property>
<property name="pane_border">1</property>
<property name="pane_position" />
<property name="pane_size" />
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos" />
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size" />
<property name="style">wxTE_DONTWRAP</property>
<property name="subclass" />
<property name="toolbar_pane">0</property>
<property name="tooltip" />
<property name="validator_data_type" />
<property name="validator_style">wxFILTER_NONE</property>
<property name="validator_type">wxDefaultValidator</property>
<property name="validator_variable" />
<property name="value" />
<property name="window_extra_style" />
<property name="window_name" />
<property name="window_style">wxHSCROLL</property>
<event name="OnChar" />
<event name="OnEnterWindow" />
<event name="OnEraseBackground" />
<event name="OnKeyDown" />
<event name="OnKeyUp" />
<event name="OnKillFocus" />
<event name="OnLeaveWindow" />
<event name="OnLeftDClick" />
<event name="OnLeftDown" />
<event name="OnLeftUp" />
<event name="OnMiddleDClick" />
<event name="OnMiddleDown" />
<event name="OnMiddleUp" />
<event name="OnMotion" />
<event name="OnMouseEvents" />
<event name="OnMouseWheel" />
<event name="OnPaint" />
<event name="OnRightDClick" />
<event name="OnRightDown" />
<event name="OnRightUp" />
<event name="OnSetFocus" />
<event name="OnSize" />
<event name="OnText" />
<event name="OnTextEnter" />
<event name="OnTextMaxLen" />
<event name="OnTextURL" />
<event name="OnUpdateUI" />
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxEXPAND</property>
<property name="proportion">1</property>
<object class="spacer" expanded="1">
<property name="height">0</property>
<property name="permission">protected</property>
<property name="width">0</property>
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">8</property>
<property name="flag">wxEXPAND</property>
<property name="proportion">1</property>
<object class="wxBoxSizer" expanded="1">
<property name="minimum_size" />
<property name="name">bSizer7</property>
<property name="orient">wxHORIZONTAL</property>
<property name="permission">none</property>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxEXPAND</property>
<property name="proportion">1</property>
<object class="spacer" expanded="1">
<property name="height">0</property>
<property name="permission">protected</property>
<property name="width">0</property>
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">2</property>
<property name="flag">wxALL</property>
<property name="proportion">0</property>
<object class="wxButton" expanded="1">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer" />
<property name="aui_name" />
<property name="aui_position" />
<property name="aui_row" />
<property name="best_size" />
<property name="bg" />
<property name="caption" />
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help" />
<property name="context_menu">1</property>
<property name="default">0</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg" />
<property name="floatable">1</property>
<property name="font" />
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">Cancel</property>
<property name="max_size" />
<property name="maximize_button">0</property>
<property name="maximum_size" />
<property name="min_size" />
<property name="minimize_button">0</property>
<property name="minimum_size" />
<property name="moveable">1</property>
<property name="name">m_cancelButton</property>
<property name="pane_border">1</property>
<property name="pane_position" />
<property name="pane_size" />
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos" />
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size" />
<property name="style" />
<property name="subclass" />
<property name="toolbar_pane">0</property>
<property name="tooltip" />
<property name="validator_data_type" />
<property name="validator_style">wxFILTER_NONE</property>
<property name="validator_type">wxDefaultValidator</property>
<property name="validator_variable" />
<property name="window_extra_style" />
<property name="window_name" />
<property name="window_style" />
<event name="OnButtonClick">OnCancelButton</event>
<event name="OnChar" />
<event name="OnEnterWindow" />
<event name="OnEraseBackground" />
<event name="OnKeyDown" />
<event name="OnKeyUp" />
<event name="OnKillFocus" />
<event name="OnLeaveWindow" />
<event name="OnLeftDClick" />
<event name="OnLeftDown" />
<event name="OnLeftUp" />
<event name="OnMiddleDClick" />
<event name="OnMiddleDown" />
<event name="OnMiddleUp" />
<event name="OnMotion" />
<event name="OnMouseEvents" />
<event name="OnMouseWheel" />
<event name="OnPaint" />
<event name="OnRightDClick" />
<event name="OnRightDown" />
<event name="OnRightUp" />
<event name="OnSetFocus" />
<event name="OnSize" />
<event name="OnUpdateUI" />
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">2</property>
<property name="flag">wxALL</property>
<property name="proportion">0</property>
<object class="wxButton" expanded="1">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer" />
<property name="aui_name" />
<property name="aui_position" />
<property name="aui_row" />
<property name="best_size" />
<property name="bg" />
<property name="caption" />
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help" />
<property name="context_menu">1</property>
<property name="default">0</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg" />
<property name="floatable">1</property>
<property name="font" />
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">Ok</property>
<property name="max_size" />
<property name="maximize_button">0</property>
<property name="maximum_size" />
<property name="min_size" />
<property name="minimize_button">0</property>
<property name="minimum_size" />
<property name="moveable">1</property>
<property name="name">m_OkButton</property>
<property name="pane_border">1</property>
<property name="pane_position" />
<property name="pane_size" />
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos" />
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size" />
<property name="style" />
<property name="subclass" />
<property name="toolbar_pane">0</property>
<property name="tooltip" />
<property name="validator_data_type" />
<property name="validator_style">wxFILTER_NONE</property>
<property name="validator_type">wxDefaultValidator</property>
<property name="validator_variable" />
<property name="window_extra_style" />
<property name="window_name" />
<property name="window_style" />
<event name="OnButtonClick">OnOkButton</event>
<event name="OnChar" />
<event name="OnEnterWindow" />
<event name="OnEraseBackground" />
<event name="OnKeyDown" />
<event name="OnKeyUp" />
<event name="OnKillFocus" />
<event name="OnLeaveWindow" />
<event name="OnLeftDClick" />
<event name="OnLeftDown" />
<event name="OnLeftUp" />
<event name="OnMiddleDClick" />
<event name="OnMiddleDown" />
<event name="OnMiddleUp" />
<event name="OnMotion" />
<event name="OnMouseEvents" />
<event name="OnMouseWheel" />
<event name="OnPaint" />
<event name="OnRightDClick" />
<event name="OnRightDown" />
<event name="OnRightUp" />
<event name="OnSetFocus" />
<event name="OnSize" />
<event name="OnUpdateUI" />
</object>
</object>
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxEXPAND</property>
<property name="proportion">1</property>
<object class="spacer" expanded="1">
<property name="height">0</property>
<property name="permission">protected</property>
<property name="width">0</property>
</object>
</object>
</object>
</object>
</object>
</wxFormBuilder_Project>

View File

@ -0,0 +1,24 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "SDRDeviceAddForm.h"
class SDRDeviceAddDialog : public SDRDeviceAddForm {
public:
explicit SDRDeviceAddDialog( wxWindow* parent );
void OnSoapyModuleChanged( wxCommandEvent& event ) override;
void OnCancelButton( wxCommandEvent& event ) override;
void OnOkButton( wxCommandEvent& event ) override;
bool wasOkPressed() const;
std::string getSelectedModule();
std::string getModuleParam();
private:
bool okPressed;
std::string selectedModule;
std::string moduleParam;
};

View File

@ -0,0 +1,83 @@
///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version Aug 8 2018)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
///////////////////////////////////////////////////////////////////////////
#include "SDRDeviceAddForm.h"
///////////////////////////////////////////////////////////////////////////
SDRDeviceAddForm::SDRDeviceAddForm( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxDialog( parent, id, title, pos, size, style )
{
this->SetSizeHints( wxDefaultSize, wxDefaultSize );
wxBoxSizer* bSizer6;
bSizer6 = new wxBoxSizer( wxVERTICAL );
m_staticText4 = new wxStaticText( this, wxID_ANY, wxT("Manually add a SoapyRemote or SoapySDR device. \n\nUseful for a device that is not detected automatically."), wxDefaultPosition, wxDefaultSize, 0 );
m_staticText4->Wrap( -1 );
bSizer6->Add( m_staticText4, 0, wxALL, 8 );
bSizer6->Add( 0, 0, 1, wxEXPAND, 5 );
wxArrayString m_soapyModuleChoices;
m_soapyModule = new wxChoice( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, m_soapyModuleChoices, 0 );
m_soapyModule->SetSelection( 0 );
bSizer6->Add( m_soapyModule, 0, wxALL, 8 );
bSizer6->Add( 0, 0, 1, wxEXPAND, 5 );
m_paramLabel = new wxStaticText( this, wxID_ANY, wxT("<Parameter>"), wxDefaultPosition, wxDefaultSize, 0 );
m_paramLabel->Wrap( -1 );
bSizer6->Add( m_paramLabel, 0, wxALL, 8 );
m_paramText = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_DONTWRAP|wxHSCROLL );
m_paramText->SetMinSize( wxSize( -1,48 ) );
bSizer6->Add( m_paramText, 1, wxALL|wxEXPAND, 8 );
bSizer6->Add( 0, 0, 1, wxEXPAND, 5 );
wxBoxSizer* bSizer7;
bSizer7 = new wxBoxSizer( wxHORIZONTAL );
bSizer7->Add( 0, 0, 1, wxEXPAND, 5 );
m_cancelButton = new wxButton( this, wxID_ANY, wxT("Cancel"), wxDefaultPosition, wxDefaultSize, 0 );
bSizer7->Add( m_cancelButton, 0, wxALL, 2 );
m_OkButton = new wxButton( this, wxID_ANY, wxT("Ok"), wxDefaultPosition, wxDefaultSize, 0 );
bSizer7->Add( m_OkButton, 0, wxALL, 2 );
bSizer6->Add( bSizer7, 1, wxEXPAND, 8 );
bSizer6->Add( 0, 0, 1, wxEXPAND, 5 );
this->SetSizer( bSizer6 );
this->Layout();
this->Centre( wxBOTH );
// Connect Events
m_soapyModule->Connect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( SDRDeviceAddForm::OnSoapyModuleChanged ), NULL, this );
m_cancelButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SDRDeviceAddForm::OnCancelButton ), NULL, this );
m_OkButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SDRDeviceAddForm::OnOkButton ), NULL, this );
}
SDRDeviceAddForm::~SDRDeviceAddForm()
{
// Disconnect Events
m_soapyModule->Disconnect( wxEVT_COMMAND_CHOICE_SELECTED, wxCommandEventHandler( SDRDeviceAddForm::OnSoapyModuleChanged ), NULL, this );
m_cancelButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SDRDeviceAddForm::OnCancelButton ), NULL, this );
m_OkButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( SDRDeviceAddForm::OnOkButton ), NULL, this );
}

View File

@ -0,0 +1,59 @@
///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version Aug 8 2018)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
///////////////////////////////////////////////////////////////////////////
#ifndef __SDRDEVICEADDFORM_H__
#define __SDRDEVICEADDFORM_H__
#include <wx/artprov.h>
#include <wx/xrc/xmlres.h>
#include <wx/string.h>
#include <wx/stattext.h>
#include <wx/gdicmn.h>
#include <wx/font.h>
#include <wx/colour.h>
#include <wx/settings.h>
#include <wx/choice.h>
#include <wx/textctrl.h>
#include <wx/bitmap.h>
#include <wx/image.h>
#include <wx/icon.h>
#include <wx/button.h>
#include <wx/sizer.h>
#include <wx/dialog.h>
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
/// Class SDRDeviceAddForm
///////////////////////////////////////////////////////////////////////////////
class SDRDeviceAddForm : public wxDialog
{
private:
protected:
wxStaticText* m_staticText4;
wxChoice* m_soapyModule;
wxStaticText* m_paramLabel;
wxTextCtrl* m_paramText;
wxButton* m_cancelButton;
wxButton* m_OkButton;
// Virtual event handlers, override them in your derived class
virtual void OnSoapyModuleChanged( wxCommandEvent& event ) { event.Skip(); }
virtual void OnCancelButton( wxCommandEvent& event ) { event.Skip(); }
virtual void OnOkButton( wxCommandEvent& event ) { event.Skip(); }
public:
SDRDeviceAddForm( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("Add SoapySDR Device"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 395,293 ), long style = wxDEFAULT_DIALOG_STYLE );
~SDRDeviceAddForm();
};
#endif //__SDRDEVICEADDFORM_H__

View File

@ -0,0 +1,628 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "SDRDevices.h"
#include <wx/msgdlg.h>
#include "CubicSDR.h"
#include <algorithm>
#ifdef __linux__
#include "CubicSDR.xpm"
#endif
SDRDevicesDialog::SDRDevicesDialog( wxWindow* parent, const wxPoint &pos): devFrame( parent, wxID_ANY, wxT(CUBICSDR_INSTALL_NAME " :: SDR Devices"), pos) {
refresh = true;
failed = false;
m_refreshButton->Disable();
m_addRemoteButton->Disable();
m_useSelectedButton->Disable();
m_deviceTimer.Start(250);
selId = nullptr;
editId = nullptr;
removeId = nullptr;
devAddDialog = nullptr;
dev = nullptr;
#ifdef __linux__
SetIcon(wxICON(cubicsdr));
#elif _WIN32
SetIcon(wxICON(frame_icon));
#endif
}
void SDRDevicesDialog::OnClose( wxCloseEvent& /* event */) {
wxGetApp().setDeviceSelectorClosed();
Destroy();
}
void SDRDevicesDialog::OnDeleteItem( wxTreeEvent& event ) {
event.Skip();
}
wxPGProperty *SDRDevicesDialog::addArgInfoProperty(wxPropertyGrid *pg, SoapySDR::ArgInfo arg) {
wxPGProperty *prop = nullptr;
int intVal;
double floatVal;
std::vector<std::string>::iterator stringIter;
switch (arg.type) {
case SoapySDR::ArgInfo::INT:
try {
intVal = std::stoi(arg.value);
} catch (const std::invalid_argument &) {
intVal = 0;
}
prop = pg->Append( new wxIntProperty(arg.name, wxPG_LABEL, intVal) );
if (arg.range.minimum() != arg.range.maximum()) {
pg->SetPropertyAttribute( prop, wxPG_ATTR_MIN, arg.range.minimum());
pg->SetPropertyAttribute( prop, wxPG_ATTR_MAX, arg.range.maximum());
}
break;
case SoapySDR::ArgInfo::FLOAT:
try {
floatVal = std::stod(arg.value);
} catch (const std::invalid_argument &) {
floatVal = 0;
}
prop = pg->Append( new wxFloatProperty(arg.name, wxPG_LABEL, floatVal) );
if (arg.range.minimum() != arg.range.maximum()) {
pg->SetPropertyAttribute( prop, wxPG_ATTR_MIN, arg.range.minimum());
pg->SetPropertyAttribute( prop, wxPG_ATTR_MAX, arg.range.maximum());
}
break;
case SoapySDR::ArgInfo::BOOL:
prop = pg->Append( new wxBoolProperty(arg.name, wxPG_LABEL, (arg.value=="true")) );
break;
case SoapySDR::ArgInfo::STRING:
if (!arg.options.empty()) {
intVal = 0;
prop = pg->Append( new wxEnumProperty(arg.name, wxPG_LABEL) );
for (stringIter = arg.options.begin(); stringIter != arg.options.end(); stringIter++) {
std::string optName = (*stringIter);
std::string displayName = optName;
if (!arg.optionNames.empty()) {
displayName = arg.optionNames[intVal];
}
prop->AddChoice(displayName);
if ((*stringIter)==arg.value) {
prop->SetChoiceSelection(intVal);
}
intVal++;
}
} else {
prop = pg->Append( new wxStringProperty(arg.name, wxPG_LABEL, arg.value) );
}
break;
}
if (prop != nullptr) {
prop->SetHelpString(arg.key + ": " + arg.description);
}
return prop;
}
void SDRDevicesDialog::refreshDeviceProperties() {
SDRDeviceInfo *selDev = getSelectedDevice(devTree->GetSelection());
if (selDev && selDev->isAvailable()) {
dev = selDev;
selId = devTree->GetSelection();
DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(dev->getName());
m_propertyGrid->Clear();
SoapySDR::Device *soapyDev = dev->getSoapyDevice();
SoapySDR::ArgInfoList args = soapyDev->getSettingInfo();
//A) General settings: name, offset, sample rate, agc, antennas (if > 1)
m_propertyGrid->Append(new wxPropertyCategory("General Settings"));
devSettings.clear();
//A-1) Name
devSettings["name"] = m_propertyGrid->Append( new wxStringProperty("Name", wxPG_LABEL, devConfig->getDeviceName()) );
//A-2) Offset
devSettings["offset"] = m_propertyGrid->Append( new wxIntProperty("Offset (KHz)", wxPG_LABEL, devConfig->getOffset() / 1000) );
//A-3) Antennas, is there are more than 1 RX antenna, else do not expose the setting.
//get the saved setting
const std::string& currentSetAntenna = devConfig->getAntennaName();
//compare to the list of existing antennas
SoapySDR::ArgInfo antennasArg;
std::vector<std::string> antennaOpts = selDev->getAntennaNames(SOAPY_SDR_RX, 0);
//only do something if there is more than 1 antenna
if (antennaOpts.size() > 1) {
//by default, choose the first of the list.
std::string antennaToSelect = antennaOpts.front();
auto found_i = std::find(antennaOpts.begin(), antennaOpts.end(), currentSetAntenna);
if (found_i != antennaOpts.end()) {
antennaToSelect = currentSetAntenna;
}
else {
//erroneous antenna name, re-write device config with the first choice of the list.
devConfig->setAntennaName(antennaToSelect);
}
//build device settings
for (const std::string& antenna : antennaOpts) {
antennasArg.options.push_back(antenna);
antennasArg.optionNames.push_back(antenna);
}
antennasArg.type = SoapySDR::ArgInfo::STRING;
antennasArg.units = "";
antennasArg.name = "Antenna";
antennasArg.key = "antenna";
antennasArg.value = antennaToSelect;
devSettings["antenna"] = addArgInfoProperty(m_propertyGrid, antennasArg);
deviceArgs["antenna"] = antennasArg;
} //end if more than 1 antenna
else {
devConfig->setAntennaName("");
}
//A-4) Sample_rate:
long currentSampleRate = wxGetApp().getSampleRate();
long deviceSampleRate = devConfig->getSampleRate();
if (!deviceSampleRate) {
deviceSampleRate = selDev->getSampleRateNear(SOAPY_SDR_RX, 0, currentSampleRate);
}
SoapySDR::ArgInfo sampleRateArg;
std::vector<long> rateOpts = selDev->getSampleRates(SOAPY_SDR_RX, 0);
for (long rate : rateOpts) {
sampleRateArg.options.push_back(std::to_string(rate));
sampleRateArg.optionNames.push_back(frequencyToStr(rate));
}
sampleRateArg.type = SoapySDR::ArgInfo::STRING;
sampleRateArg.units = "Hz";
sampleRateArg.name = "Sample Rate";
sampleRateArg.key = "sample_rate";
sampleRateArg.value = std::to_string(deviceSampleRate);
devSettings["sample_rate"] = addArgInfoProperty(m_propertyGrid, sampleRateArg);
deviceArgs["sample_rate"] = sampleRateArg;
//B) Runtime Settings:
runtimeArgs.clear();
runtimeProps.clear();
streamProps.clear();
if (!args.empty()) {
m_propertyGrid->Append(new wxPropertyCategory("Run-time Settings"));
for (const auto & args_i : args) {
SoapySDR::ArgInfo arg = args_i;
//We-reread the Device configuration, else we use the user settings.
if (dev) {
//Apply saved settings
DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(dev->getDeviceId());
arg.value = devConfig->getSetting(arg.key, soapyDev->readSetting(arg.key)); //use SoapyDevice data as fallback.
}
else {
//re-read the SoapyDevice
arg.value = soapyDev->readSetting(arg.key);
}
runtimeProps[arg.key] = addArgInfoProperty(m_propertyGrid, arg);
runtimeArgs[arg.key] = arg;
}
}
if (dev) {
args = dev->getSoapyDevice()->getStreamArgsInfo(SOAPY_SDR_RX, 0);
DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(dev->getDeviceId());
ConfigSettings devStreamOpts = devConfig->getStreamOpts();
if (!devStreamOpts.empty()) {
for (auto & arg : args) {
if (devStreamOpts.find(arg.key) != devStreamOpts.end()) {
arg.value = devStreamOpts[arg.key];
}
}
}
if (!args.empty()) {
m_propertyGrid->Append(new wxPropertyCategory("Stream Settings"));
for (const SoapySDR::ArgInfo& arg : args) {
streamProps[arg.key] = addArgInfoProperty(m_propertyGrid, arg);
}
}
}
if (selDev->isManual()) {
m_addRemoteButton->SetLabel("Remove");
removeId = selId;
} else {
m_addRemoteButton->SetLabel("Add");
removeId = nullptr;
}
} else if (selDev && !selDev->isAvailable() && selDev->isManual()) {
m_propertyGrid->Clear();
devSettings.clear();
runtimeArgs.clear();
runtimeProps.clear();
streamProps.clear();
removeId = devTree->GetSelection();
dev = nullptr;
selId = nullptr;
editId = nullptr;
m_addRemoteButton->SetLabel("Remove");
} else if (!selDev) {
m_addRemoteButton->SetLabel("Add");
removeId = nullptr;
}
}
void SDRDevicesDialog::OnSelectionChanged( wxTreeEvent& event ) {
refreshDeviceProperties();
event.Skip();
}
void SDRDevicesDialog::OnAddRemote( wxMouseEvent& /* event */) {
if (removeId != nullptr) {
SDRDeviceInfo *selDev = getSelectedDevice(removeId);
if (selDev) {
SDREnumerator::removeManual(selDev->getDriver(),selDev->getManualParams());
m_propertyGrid->Clear();
devSettings.clear();
runtimeArgs.clear();
runtimeProps.clear();
streamProps.clear();
dev = nullptr;
selId = nullptr;
editId = nullptr;
devTree->Delete(removeId);
removeId = nullptr;
m_addRemoteButton->SetLabel("Add");
}
return;
}
devAddDialog = new SDRDeviceAddDialog(this);
devAddDialog->ShowModal();
if (devAddDialog->wasOkPressed()) {
std::string module = devAddDialog->getSelectedModule();
if (module == "SoapyRemote") {
if (!SDREnumerator::hasRemoteModule()) {
wxMessageDialog *info;
info = new wxMessageDialog(nullptr, wxT("Install SoapyRemote module to add remote servers.\n\nhttps://github.com/pothosware/SoapyRemote"), wxT("SoapyRemote not found."), wxOK | wxICON_ERROR);
info->ShowModal();
return;
}
wxString remoteAddr = devAddDialog->getModuleParam();
if (!remoteAddr.Trim().empty()) {
wxGetApp().addRemote(remoteAddr.Trim().ToStdString());
}
devTree->Disable();
m_addRemoteButton->Disable();
m_useSelectedButton->Disable();
refresh = true;
} else {
std::string mod = devAddDialog->getSelectedModule();
std::string param = devAddDialog->getModuleParam();
SDREnumerator::addManual(mod, param);
doRefreshDevices();
}
}
}
SDRDeviceInfo *SDRDevicesDialog::getSelectedDevice(wxTreeItemId selId_in) {
devItems_i = devItems.find(selId_in);
if (devItems_i != devItems.end()) {
return devItems[selId_in];
}
return nullptr;
}
void SDRDevicesDialog::OnUseSelected( wxMouseEvent& event) {
if (dev != nullptr) {
SoapySDR::ArgInfoList args = dev->getSoapyDevice()->getSettingInfo();
SoapySDR::Kwargs settingArgs;
SoapySDR::Kwargs streamArgs;
for (const SoapySDR::ArgInfo& arg : args) {
wxPGProperty *prop = runtimeProps[arg.key];
if (arg.type == SoapySDR::ArgInfo::STRING && !arg.options.empty()) {
settingArgs[arg.key] = getSelectedChoiceOption(prop, arg);
} else if (arg.type == SoapySDR::ArgInfo::BOOL) {
settingArgs[arg.key] = (prop->GetValueAsString()=="True")?"true":"false";
} else {
settingArgs[arg.key] = prop->GetValueAsString();
}
}
if (dev) {
args = dev->getSoapyDevice()->getStreamArgsInfo(SOAPY_SDR_RX, 0);
if (!args.empty()) {
for (const auto & args_i : args) {
SoapySDR::ArgInfo arg = args_i;
wxPGProperty *prop = streamProps[arg.key];
if (arg.type == SoapySDR::ArgInfo::STRING && !arg.options.empty()) {
streamArgs[arg.key] = getSelectedChoiceOption(prop, arg);
} else if (arg.type == SoapySDR::ArgInfo::BOOL) {
streamArgs[arg.key] = (prop->GetValueAsString()=="True")?"true":"false";
} else {
streamArgs[arg.key] = prop->GetValueAsString();
}
}
}
}
AppConfig *cfg = wxGetApp().getConfig();
DeviceConfig *devConfig = cfg->getDevice(dev->getDeviceId());
devConfig->setSettings(settingArgs);
devConfig->setStreamOpts(streamArgs);
wxGetApp().setDeviceArgs(settingArgs);
wxGetApp().setStreamArgs(streamArgs);
wxGetApp().setDevice(dev,0);
//update main application title with Device name:
wxString titleBar = CUBICSDR_TITLE;
titleBar += " - " + wxGetApp().getDevice()->getName();
wxGetApp().getAppFrame()->SetTitle(titleBar);
Close();
}
event.Skip();
}
void SDRDevicesDialog::OnTreeDoubleClick( wxMouseEvent& event ) {
OnUseSelected(event);
}
void SDRDevicesDialog::OnDeviceTimer( wxTimerEvent& event ) {
if (refresh) {
if (wxGetApp().areModulesMissing()) {
if (!failed) {
failed = true;
wxMessageDialog *info;
info = new wxMessageDialog(nullptr, wxT("\nNo SoapySDR modules were found.\n\nCubicSDR requires at least one SoapySDR device support module to be installed.\n\nPlease visit https://github.com/cjcliffe/CubicSDR/wiki and in the build instructions for your platform read the 'Support Modules' section for more information."), wxT("\x28\u256F\xB0\u25A1\xB0\uFF09\u256F\uFE35\x20\u253B\u2501\u253B"), wxOK | wxICON_ERROR);
info->ShowModal();
}
return;
}
if (wxGetApp().areDevicesEnumerating() || !wxGetApp().areDevicesReady()) {
std::string msg = wxGetApp().getNotification();
devStatusBar->SetStatusText(msg);
devTree->DeleteAllItems();
devTree->AddRoot(msg);
event.Skip();
return;
}
devTree->DeleteAllItems();
wxTreeItemId devRoot = devTree->AddRoot("Devices");
wxTreeItemId localBranch = devTree->AppendItem(devRoot, "Local");
wxTreeItemId dsBranch = devTree->AppendItem(devRoot, "Local Net");
wxTreeItemId remoteBranch = devTree->AppendItem(devRoot, "Remote");
wxTreeItemId manualBranch = devTree->AppendItem(devRoot, "Manual");
devs[""] = SDREnumerator::enumerate_devices("",true);
if (devs[""] != nullptr) {
for (devs_i = devs[""]->begin(); devs_i != devs[""]->end(); devs_i++) {
DeviceConfig *devConfig;
if ((*devs_i)->isManual()) {
std::string devName = "Unknown";
if ((*devs_i)->isAvailable()) {
devConfig = wxGetApp().getConfig()->getDevice((*devs_i)->getDeviceId());
devName = devConfig->getDeviceName();
} else {
devName = (*devs_i)->getDeviceId();
}
devItems[devTree->AppendItem(manualBranch, devName)] = (*devs_i);
} else if ((*devs_i)->isRemote()) {
devConfig = wxGetApp().getConfig()->getDevice((*devs_i)->getDeviceId());
devItems[devTree->AppendItem(dsBranch, devConfig->getDeviceName())] = (*devs_i);
} else {
devConfig = wxGetApp().getConfig()->getDevice((*devs_i)->getDeviceId());
devItems[devTree->AppendItem(localBranch, devConfig->getDeviceName())] = (*devs_i);
}
}
}
std::vector<std::string> remotes = SDREnumerator::getRemotes();
std::vector<SDRDeviceInfo *>::iterator remoteDevs_i;
if (!remotes.empty()) {
for (const std::string& remote : remotes) {
devs[remote] = SDREnumerator::enumerate_devices(remote, true);
DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(remote);
wxTreeItemId remoteNode = devTree->AppendItem(remoteBranch, devConfig->getDeviceName());
if (devs[remote] != nullptr) {
for (remoteDevs_i = devs[remote]->begin(); remoteDevs_i != devs[remote]->end(); remoteDevs_i++) {
devItems[devTree->AppendItem(remoteNode, (*remoteDevs_i)->getName())] = (*remoteDevs_i);
}
}
}
}
m_refreshButton->Enable();
m_addRemoteButton->Enable();
m_useSelectedButton->Enable();
devTree->Enable();
devTree->ExpandAll();
devStatusBar->SetStatusText("Ready.");
refresh = false;
}
}
void SDRDevicesDialog::OnRefreshDevices( wxMouseEvent& /* event */) {
doRefreshDevices();
}
std::string SDRDevicesDialog::getSelectedChoiceOption(wxPGProperty* prop, const SoapySDR::ArgInfo& arg) {
std::string optionName;
int choiceIndex = prop->GetChoiceSelection();
if (!arg.options.empty()) {
int choiceMax = arg.options.size();
if (choiceIndex >= 0 && choiceIndex < choiceMax) {
//normal selection
optionName = arg.options[choiceIndex];
} else {
//choose the first one of the list:
optionName = arg.options[0];
prop->SetChoiceSelection(0);
}
}
return optionName;
}
void SDRDevicesDialog::OnPropGridChanged( wxPropertyGridEvent& event ) {
if (event.GetProperty() == devSettings["name"]) {
DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(dev->getDeviceId());
wxString devName = event.GetPropertyValue().GetString();
devConfig->setDeviceName(devName.ToStdString());
if (editId) {
devTree->SetItemText(editId, devConfig->getDeviceName());
}
if (devName.empty()) {
event.GetProperty()->SetValueFromString(devConfig->getDeviceName());
}
} else if (dev && event.GetProperty() == devSettings["offset"]) {
DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(dev->getDeviceId());
long offset_In_KHz = event.GetPropertyValue().GetInteger();
devConfig->setOffset((long long) offset_In_KHz * 1000);
if (dev->isActive() || !wxGetApp().getDevice()) {
wxGetApp().setOffset((long long)offset_In_KHz * 1000);
}
}
else if (dev && event.GetProperty() == devSettings["sample_rate"]) {
DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(dev->getDeviceId());
std::string strRate = deviceArgs["sample_rate"].options[event.GetPropertyValue().GetInteger()];
long srate = 0;
try {
srate = std::stol(strRate);
devConfig->setSampleRate(srate);
if (dev->isActive() || !wxGetApp().getDevice()) {
wxGetApp().setSampleRate(srate);
}
} catch (const std::invalid_argument &) {
// nop
}
} else if (dev && event.GetProperty() == devSettings["antenna"]) {
DeviceConfig *devConfig = wxGetApp().getConfig()->getDevice(dev->getDeviceId());
std::string strAntennaName = deviceArgs["antenna"].options[event.GetPropertyValue().GetInteger()];
try {
devConfig->setAntennaName(strAntennaName);
if (dev->isActive() || !wxGetApp().getDevice()) {
wxGetApp().setAntennaName(strAntennaName);
}
}
catch (const std::invalid_argument &) {
// nop
}
}
else if (dev) {
wxPGProperty *prop = event.GetProperty();
//change value of RuntimeProps
for (auto & runtimeProp : runtimeProps) {
if (runtimeProp.second == prop) {
SoapySDR::Device *soapyDev = dev->getSoapyDevice();
std::string settingValue = prop->GetValueAsString().ToStdString();
SoapySDR::ArgInfo arg = runtimeArgs[runtimeProp.first];
if (arg.type == SoapySDR::ArgInfo::STRING && !arg.options.empty()) {
settingValue = getSelectedChoiceOption(prop, arg);
} else if (arg.type == SoapySDR::ArgInfo::BOOL) {
settingValue = (prop->GetValueAsString()=="True")?"true":"false";
} else {
settingValue = prop->GetValueAsString();
}
soapyDev->writeSetting(runtimeProp.first, settingValue);
if (dev->isActive()) {
wxGetApp().getSDRThread()->writeSetting(runtimeProp.first, settingValue);
}
return;
}
}
}
}
void SDRDevicesDialog::OnPropGridFocus( wxFocusEvent& /* event */) {
editId = selId;
}
void SDRDevicesDialog::doRefreshDevices() {
selId = nullptr;
editId = nullptr;
removeId = nullptr;
dev = nullptr;
wxGetApp().stopDevice(false, 2000);
devTree->DeleteAllItems();
devTree->Disable();
m_propertyGrid->Clear();
devSettings.clear();
runtimeArgs.clear();
runtimeProps.clear();
streamProps.clear();
m_refreshButton->Disable();
m_addRemoteButton->Disable();
m_useSelectedButton->Disable();
wxGetApp().reEnumerateDevices();
refresh = true;
m_addRemoteButton->SetLabel("Add");
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,55 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include <map>
#include <vector>
#include "SDRDevicesForm.h"
#include "SoapySDRThread.h"
#include "SDREnumerator.h"
#include "SDRDeviceAdd.h"
class SDRDevicesDialog: public devFrame {
public:
explicit SDRDevicesDialog( wxWindow* parent, const wxPoint &wxPos = wxDefaultPosition);
void OnClose( wxCloseEvent& event ) override;
void OnDeleteItem( wxTreeEvent& event ) override;
void OnSelectionChanged( wxTreeEvent& event ) override;
void OnAddRemote( wxMouseEvent& event ) override;
void OnUseSelected( wxMouseEvent& event ) override;
void OnTreeDoubleClick( wxMouseEvent& event ) override;
void OnDeviceTimer( wxTimerEvent& event ) override;
void OnRefreshDevices( wxMouseEvent& event ) override;
void OnPropGridChanged( wxPropertyGridEvent& event ) override;
void OnPropGridFocus( wxFocusEvent& event ) override;
private:
void refreshDeviceProperties();
void doRefreshDevices();
SDRDeviceInfo *getSelectedDevice(wxTreeItemId selId_in);
wxPGProperty *addArgInfoProperty(wxPropertyGrid *pg, SoapySDR::ArgInfo arg);
//
std::string getSelectedChoiceOption(wxPGProperty* prop, const SoapySDR::ArgInfo& arg);
bool refresh, failed;
std::map<std::string, std::vector<SDRDeviceInfo *>* > devs;
std::vector<SDRDeviceInfo *>::iterator devs_i;
std::map<wxTreeItemId, SDRDeviceInfo *> devItems;
std::map<wxTreeItemId, SDRDeviceInfo *>::iterator devItems_i;
SDRDeviceInfo *dev;
std::map<std::string, SoapySDR::ArgInfo> deviceArgs;
std::map<std::string, wxPGProperty *> runtimeProps;
std::map<std::string, SoapySDR::ArgInfo> runtimeArgs;
std::map<std::string, wxPGProperty *> streamProps;
std::map<std::string, wxPGProperty *> devSettings;
wxTreeItemId selId;
wxTreeItemId editId;
wxTreeItemId removeId;
SDRDeviceAddDialog *devAddDialog;
};

View File

@ -0,0 +1,115 @@
///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version Aug 8 2018)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
///////////////////////////////////////////////////////////////////////////
#include "SDRDevicesForm.h"
///////////////////////////////////////////////////////////////////////////
devFrame::devFrame( wxWindow* parent, wxWindowID id, const wxString& title, const wxPoint& pos, const wxSize& size, long style ) : wxFrame( parent, id, title, pos, size, style )
{
this->SetSizeHints( wxDefaultSize, wxDefaultSize );
devStatusBar = this->CreateStatusBar( 1, wxSTB_SIZEGRIP, wxID_ANY );
wxBoxSizer* devFrameSizer;
devFrameSizer = new wxBoxSizer( wxVERTICAL );
m_panel3 = new wxPanel( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
wxBoxSizer* bSizer4;
bSizer4 = new wxBoxSizer( wxHORIZONTAL );
m_panel6 = new wxPanel( m_panel3, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
wxBoxSizer* bSizer6;
bSizer6 = new wxBoxSizer( wxVERTICAL );
devTree = new wxTreeCtrl( m_panel6, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTR_DEFAULT_STYLE );
devTree->Enable( false );
bSizer6->Add( devTree, 1, wxEXPAND, 5 );
m_panel4 = new wxPanel( m_panel6, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
wxBoxSizer* bSizer5;
bSizer5 = new wxBoxSizer( wxHORIZONTAL );
m_refreshButton = new wxButton( m_panel4, wxID_ANY, wxT("Refresh"), wxDefaultPosition, wxDefaultSize, 0 );
bSizer5->Add( m_refreshButton, 1, wxALL, 5 );
m_addRemoteButton = new wxButton( m_panel4, wxID_ANY, wxT("Add"), wxDefaultPosition, wxDefaultSize, 0 );
bSizer5->Add( m_addRemoteButton, 1, wxALL, 5 );
m_useSelectedButton = new wxButton( m_panel4, wxID_ANY, wxT("Start"), wxDefaultPosition, wxDefaultSize, 0 );
bSizer5->Add( m_useSelectedButton, 1, wxALL|wxALIGN_CENTER_VERTICAL, 5 );
m_panel4->SetSizer( bSizer5 );
m_panel4->Layout();
bSizer5->Fit( m_panel4 );
bSizer6->Add( m_panel4, 0, wxEXPAND, 5 );
m_panel6->SetSizer( bSizer6 );
m_panel6->Layout();
bSizer6->Fit( m_panel6 );
bSizer4->Add( m_panel6, 1, wxEXPAND | wxALL, 5 );
m_panel61 = new wxPanel( m_panel3, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
wxBoxSizer* bSizer7;
bSizer7 = new wxBoxSizer( wxVERTICAL );
m_staticText1 = new wxStaticText( m_panel61, wxID_ANY, wxT("SoapySDR Device Options"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticText1->Wrap( -1 );
bSizer7->Add( m_staticText1, 0, wxALL, 5 );
m_propertyGrid = new wxPropertyGrid(m_panel61, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxPG_DEFAULT_STYLE);
bSizer7->Add( m_propertyGrid, 1, wxALL|wxEXPAND, 5 );
m_panel61->SetSizer( bSizer7 );
m_panel61->Layout();
bSizer7->Fit( m_panel61 );
bSizer4->Add( m_panel61, 1, wxEXPAND | wxALL, 5 );
m_panel3->SetSizer( bSizer4 );
m_panel3->Layout();
bSizer4->Fit( m_panel3 );
devFrameSizer->Add( m_panel3, 1, wxEXPAND | wxALL, 5 );
this->SetSizer( devFrameSizer );
this->Layout();
m_deviceTimer.SetOwner( this, wxID_ANY );
this->Centre( wxBOTH );
// Connect Events
this->Connect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( devFrame::OnClose ) );
devTree->Connect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( devFrame::OnTreeDoubleClick ), NULL, this );
devTree->Connect( wxEVT_COMMAND_TREE_DELETE_ITEM, wxTreeEventHandler( devFrame::OnDeleteItem ), NULL, this );
devTree->Connect( wxEVT_COMMAND_TREE_SEL_CHANGED, wxTreeEventHandler( devFrame::OnSelectionChanged ), NULL, this );
m_refreshButton->Connect( wxEVT_LEFT_UP, wxMouseEventHandler( devFrame::OnRefreshDevices ), NULL, this );
m_addRemoteButton->Connect( wxEVT_LEFT_UP, wxMouseEventHandler( devFrame::OnAddRemote ), NULL, this );
m_useSelectedButton->Connect( wxEVT_LEFT_UP, wxMouseEventHandler( devFrame::OnUseSelected ), NULL, this );
m_propertyGrid->Connect( wxEVT_PG_CHANGED, wxPropertyGridEventHandler( devFrame::OnPropGridChanged ), NULL, this );
m_propertyGrid->Connect( wxEVT_SET_FOCUS, wxFocusEventHandler( devFrame::OnPropGridFocus ), NULL, this );
this->Connect( wxID_ANY, wxEVT_TIMER, wxTimerEventHandler( devFrame::OnDeviceTimer ) );
}
devFrame::~devFrame()
{
// Disconnect Events
this->Disconnect( wxEVT_CLOSE_WINDOW, wxCloseEventHandler( devFrame::OnClose ) );
devTree->Disconnect( wxEVT_LEFT_DCLICK, wxMouseEventHandler( devFrame::OnTreeDoubleClick ), NULL, this );
devTree->Disconnect( wxEVT_COMMAND_TREE_DELETE_ITEM, wxTreeEventHandler( devFrame::OnDeleteItem ), NULL, this );
devTree->Disconnect( wxEVT_COMMAND_TREE_SEL_CHANGED, wxTreeEventHandler( devFrame::OnSelectionChanged ), NULL, this );
m_refreshButton->Disconnect( wxEVT_LEFT_UP, wxMouseEventHandler( devFrame::OnRefreshDevices ), NULL, this );
m_addRemoteButton->Disconnect( wxEVT_LEFT_UP, wxMouseEventHandler( devFrame::OnAddRemote ), NULL, this );
m_useSelectedButton->Disconnect( wxEVT_LEFT_UP, wxMouseEventHandler( devFrame::OnUseSelected ), NULL, this );
m_propertyGrid->Disconnect( wxEVT_PG_CHANGED, wxPropertyGridEventHandler( devFrame::OnPropGridChanged ), NULL, this );
m_propertyGrid->Disconnect( wxEVT_SET_FOCUS, wxFocusEventHandler( devFrame::OnPropGridFocus ), NULL, this );
this->Disconnect( wxID_ANY, wxEVT_TIMER, wxTimerEventHandler( devFrame::OnDeviceTimer ) );
}

View File

@ -0,0 +1,77 @@
///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version Aug 8 2018)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
///////////////////////////////////////////////////////////////////////////
#ifndef __SDRDEVICESFORM_H__
#define __SDRDEVICESFORM_H__
#include <wx/artprov.h>
#include <wx/xrc/xmlres.h>
#include <wx/statusbr.h>
#include <wx/gdicmn.h>
#include <wx/font.h>
#include <wx/colour.h>
#include <wx/settings.h>
#include <wx/string.h>
#include <wx/treectrl.h>
#include <wx/bitmap.h>
#include <wx/image.h>
#include <wx/icon.h>
#include <wx/button.h>
#include <wx/sizer.h>
#include <wx/panel.h>
#include <wx/stattext.h>
#include <wx/propgrid/propgrid.h>
#include <wx/propgrid/advprops.h>
#include <wx/timer.h>
#include <wx/frame.h>
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
/// Class devFrame
///////////////////////////////////////////////////////////////////////////////
class devFrame : public wxFrame
{
private:
protected:
wxStatusBar* devStatusBar;
wxPanel* m_panel3;
wxPanel* m_panel6;
wxTreeCtrl* devTree;
wxPanel* m_panel4;
wxButton* m_refreshButton;
wxButton* m_addRemoteButton;
wxButton* m_useSelectedButton;
wxPanel* m_panel61;
wxStaticText* m_staticText1;
wxPropertyGrid* m_propertyGrid;
wxTimer m_deviceTimer;
// Virtual event handlers, override them in your derived class
virtual void OnClose( wxCloseEvent& event ) { event.Skip(); }
virtual void OnTreeDoubleClick( wxMouseEvent& event ) { event.Skip(); }
virtual void OnDeleteItem( wxTreeEvent& event ) { event.Skip(); }
virtual void OnSelectionChanged( wxTreeEvent& event ) { event.Skip(); }
virtual void OnRefreshDevices( wxMouseEvent& event ) { event.Skip(); }
virtual void OnAddRemote( wxMouseEvent& event ) { event.Skip(); }
virtual void OnUseSelected( wxMouseEvent& event ) { event.Skip(); }
virtual void OnPropGridChanged( wxPropertyGridEvent& event ) { event.Skip(); }
virtual void OnPropGridFocus( wxFocusEvent& event ) { event.Skip(); }
virtual void OnDeviceTimer( wxTimerEvent& event ) { event.Skip(); }
public:
devFrame( wxWindow* parent, wxWindowID id = wxID_ANY, const wxString& title = wxT("CubicSDR :: SDR Devices"), const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( 700,467 ), long style = wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL );
~devFrame();
};
#endif //__SDRDEVICESFORM_H__

View File

@ -0,0 +1,117 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "Modem.h"
ModemFactoryList Modem::modemFactories;
DefaultRatesList Modem::modemDefaultRates;
//! Create an empty range (0.0, 0.0)
ModemRange::ModemRange() {
_min = 0;
_max = 0;
}
//! Create a min/max range
ModemRange::ModemRange(const double minimum, const double maximum) {
_min = minimum;
_max = maximum;
}
//! Get the range minimum
double ModemRange::minimum() const {
return _min;
}
//! Get the range maximum
double ModemRange::maximum() const {
return _max;
}
ModemArgInfo::ModemArgInfo() = default;
Modem::Modem() {
useSignalOutput(false);
}
Modem::~Modem() = default;
void Modem::addModemFactory(ModemFactoryFn factoryFunc, std::string modemName, int defaultRate) {
modemFactories[modemName] = factoryFunc;
modemDefaultRates[modemName] = defaultRate;
}
ModemFactoryList Modem::getFactories() {
return modemFactories;
}
Modem *Modem::makeModem(std::string modemName) {
if (modemFactories.find(modemName) != modemFactories.end()) {
return (Modem *)modemFactories[modemName]();
}
return nullptr;
}
int Modem::getModemDefaultSampleRate(std::string modemName) {
if (modemDefaultRates.find(modemName) != modemDefaultRates.end()) {
return modemDefaultRates[modemName];
}
return 0;
}
ModemArgInfoList Modem::getSettings() {
ModemArgInfoList args;
return args;
}
int Modem::getDefaultSampleRate() {
return 200000;
}
void Modem::writeSetting(std::string /* setting */, std::string /* value */) {
// ...
}
std::string Modem::readSetting(std::string /* setting */) {
return "";
}
void Modem::writeSettings(ModemSettings settings) {
for (ModemSettings::const_iterator i = settings.begin(); i != settings.end(); i++) {
writeSetting(i->first, i->second);
}
}
ModemSettings Modem::readSettings() {
ModemArgInfoList args = getSettings();
ModemSettings rs;
for (const auto & arg : args) {
rs[arg.key] = readSetting(arg.key);
}
return rs;
}
bool Modem::shouldRebuildKit() {
return refreshKit.load();
}
void Modem::rebuildKit() {
refreshKit.store(true);
}
void Modem::clearRebuildKit() {
refreshKit.store(false);
}
bool Modem::useSignalOutput() {
return _useSignalOutput.load();
}
void Modem::useSignalOutput(bool useOutput) {
_useSignalOutput.store(useOutput);
}

View File

@ -0,0 +1,166 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "liquid/liquid.h"
#include "IOThread.h"
#include "AudioThread.h"
#include <cmath>
#include <atomic>
#include <memory>
#define MIN_BANDWIDTH 500
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
class ModemKit {
public:
ModemKit() : sampleRate(0), audioSampleRate(0) {
}
long long sampleRate;
int audioSampleRate;
};
class ModemIQData {
public:
std::vector<liquid_float_complex> data;
long long sampleRate;
ModemIQData() : sampleRate(0) {
}
virtual ~ModemIQData() = default;
};
typedef std::shared_ptr<ModemIQData> ModemIQDataPtr;
// Copy of SoapySDR::Range, original comments
class ModemRange
{
public:
//! Create an empty range (0.0, 0.0)
ModemRange();
//! Create a min/max range
ModemRange(double minimum, double maximum);
//! Get the range minimum
double minimum() const;
//! Get the range maximum
double maximum() const;
private:
double _min, _max;
};
// Modified version of SoapySDR::ArgInfo, original comments
class ModemArgInfo
{
public:
//! Default constructor
ModemArgInfo();
//! The key used to identify the argument (required)
std::string key;
/*!
* The default value of the argument when not specified (required)
* Numbers should use standard floating point and integer formats.
* Boolean values should be represented as "true" and "false".
*/
std::string value;
//! The displayable name of the argument (optional, use key if empty)
std::string name;
//! A brief description about the argument (optional)
std::string description;
//! The units of the argument: dB, Hz, etc (optional)
std::string units;
//! The data type of the argument (required)
enum class Type { BOOL, INT, FLOAT, STRING, PATH_DIR, PATH_FILE, COLOR } type;
/*!
* The range of possible numeric values (optional)
* When specified, the argument should be restricted to this range.
* The range is only applicable to numeric argument types.
*/
ModemRange range;
/*!
* A discrete list of possible values (optional)
* When specified, the argument should be restricted to this options set.
*/
std::vector<std::string> options;
/*!
* A discrete list of displayable names for the enumerated options (optional)
* When not specified, the option value itself can be used as a display name.
*/
std::vector<std::string> optionNames;
};
typedef std::vector<ModemArgInfo> ModemArgInfoList;
class ModemBase {
};
typedef ModemBase *(*ModemFactoryFn)();
typedef std::map<std::string, ModemFactoryFn> ModemFactoryList;
typedef std::map<std::string, int> DefaultRatesList;
typedef std::map<std::string, std::string> ModemSettings;
class Modem : public ModemBase {
public:
static void addModemFactory(ModemFactoryFn, std::string modemName, int defaultRate);
static ModemFactoryList getFactories();
static Modem *makeModem(std::string modemName);
static int getModemDefaultSampleRate(std::string modemName);
virtual std::string getType() = 0;
virtual std::string getName() = 0;
Modem();
virtual ~Modem();
virtual ModemArgInfoList getSettings();
virtual int getDefaultSampleRate();
virtual void writeSetting(std::string setting, std::string value);
virtual void writeSettings(ModemSettings settings);
virtual std::string readSetting(std::string setting);
virtual ModemSettings readSettings();
virtual int checkSampleRate(long long sampleRate, int audioSampleRate) = 0;
virtual ModemKit *buildKit(long long sampleRate, int audioSampleRate) = 0;
virtual void disposeKit(ModemKit *kit) = 0;
virtual void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) = 0;
bool shouldRebuildKit();
void rebuildKit();
void clearRebuildKit();
bool useSignalOutput();
void useSignalOutput(bool useOutput);
private:
static ModemFactoryList modemFactories;
static DefaultRatesList modemDefaultRates;
std::atomic_bool refreshKit, _useSignalOutput;
};

View File

@ -0,0 +1,101 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "ModemAnalog.h"
ModemAnalog::ModemAnalog() : Modem(), aOutputCeil(1), aOutputCeilMA(1), aOutputCeilMAA(1) {
}
std::string ModemAnalog::getType() {
return "analog";
}
int ModemAnalog::checkSampleRate(long long sampleRate, int /* audioSampleRate */) {
if (sampleRate < MIN_BANDWIDTH) {
return MIN_BANDWIDTH;
}
return (int)sampleRate;
}
ModemKit *ModemAnalog::buildKit(long long sampleRate, int audioSampleRate) {
auto *akit = new ModemKitAnalog;
// stop-band attenuation [dB]
float As = 60.0f;
akit->sampleRate = sampleRate;
akit->audioSampleRate = audioSampleRate;
akit->audioResampleRatio = double(audioSampleRate) / double(sampleRate);
akit->audioResampler = msresamp_rrrf_create((float)akit->audioResampleRatio, As);
return akit;
}
void ModemAnalog::disposeKit(ModemKit *kit) {
auto *akit = (ModemKitAnalog *)kit;
msresamp_rrrf_destroy(akit->audioResampler);
delete akit;
}
void ModemAnalog::initOutputBuffers(ModemKitAnalog *akit, ModemIQData *input) {
bufSize = input->data.size();
if (!bufSize) {
return;
}
double audio_resample_ratio = akit->audioResampleRatio;
size_t audio_out_size = (size_t)ceil((double) (bufSize) * audio_resample_ratio) + 512;
if (demodOutputData.size() != bufSize) {
if (demodOutputData.capacity() < bufSize) {
demodOutputData.reserve(bufSize);
}
demodOutputData.resize(bufSize);
}
if (resampledOutputData.size() != audio_out_size) {
if (resampledOutputData.capacity() < audio_out_size) {
resampledOutputData.reserve(audio_out_size);
}
resampledOutputData.resize(audio_out_size);
}
}
void ModemAnalog::buildAudioOutput(ModemKitAnalog *akit, AudioThreadInput *audioOut, bool autoGain) {
unsigned int numAudioWritten;
if (autoGain) {
aOutputCeilMA = aOutputCeilMA + (aOutputCeil - aOutputCeilMA) * 0.025f;
aOutputCeilMAA = aOutputCeilMAA + (aOutputCeilMA - aOutputCeilMAA) * 0.025f;
aOutputCeil = 0;
for (size_t i = 0; i < bufSize; i++) {
if (demodOutputData[i] > aOutputCeil) {
aOutputCeil = demodOutputData[i];
}
}
float gain = 0.5f / aOutputCeilMAA;
for (size_t i = 0; i < bufSize; i++) {
demodOutputData[i] *= gain;
}
}
msresamp_rrrf_execute(akit->audioResampler, &demodOutputData[0], (int)demodOutputData.size(), &resampledOutputData[0], &numAudioWritten);
audioOut->channels = 1;
audioOut->sampleRate = akit->audioSampleRate;
audioOut->data.assign(resampledOutputData.begin(), resampledOutputData.begin() + numAudioWritten);
}
std::vector<float> *ModemAnalog::getDemodOutputData() {
return &demodOutputData;
}
std::vector<float> *ModemAnalog::getResampledOutputData() {
return &resampledOutputData;
}

View File

@ -0,0 +1,37 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "Modem.h"
class ModemKitAnalog : public ModemKit {
public:
ModemKitAnalog() : ModemKit(), audioResampler(nullptr), audioResampleRatio(0) {
};
msresamp_rrrf audioResampler;
double audioResampleRatio;
};
class ModemAnalog : public Modem {
public:
ModemAnalog();
std::string getType() override;
int checkSampleRate(long long sampleRate, int audioSampleRate) override;
ModemKit *buildKit(long long sampleRate, int audioSampleRate) override;
void disposeKit(ModemKit *kit) override;
virtual void initOutputBuffers(ModemKitAnalog *akit, ModemIQData *input);
virtual void buildAudioOutput(ModemKitAnalog *akit, AudioThreadInput *audioOut, bool autoGain);
virtual std::vector<float> *getDemodOutputData();
virtual std::vector<float> *getResampledOutputData();
protected:
size_t bufSize;
std::vector<float> demodOutputData;
std::vector<float> resampledOutputData;
float aOutputCeil;
float aOutputCeilMA;
float aOutputCeilMAA;
};

View File

@ -0,0 +1,81 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "ModemDigital.h"
ModemDigitalOutput::ModemDigitalOutput() = default;
ModemDigital::ModemDigital() : Modem() {
#if ENABLE_DIGITAL_LAB
digitalOut = nullptr;
#endif
}
ModemDigitalOutput::~ModemDigitalOutput() = default;
std::string ModemDigital::getType() {
return "digital";
}
int ModemDigital::checkSampleRate(long long sampleRate, int /* audioSampleRate */) {
if (sampleRate < MIN_BANDWIDTH) {
return MIN_BANDWIDTH;
}
return (int)sampleRate;
}
ModemKit *ModemDigital::buildKit(long long sampleRate, int audioSampleRate) {
auto *dkit = new ModemKitDigital;
dkit->sampleRate = sampleRate;
dkit->audioSampleRate = audioSampleRate;
return dkit;
}
void ModemDigital::disposeKit(ModemKit *kit) {
auto *dkit = (ModemKitDigital *)kit;
delete dkit;
}
void ModemDigital::setDemodulatorLock(bool demod_lock_in) {
currentDemodLock.store(demod_lock_in);
}
int ModemDigital::getDemodulatorLock() {
return currentDemodLock.load();
}
void ModemDigital::updateDemodulatorLock(modemcf mod, float sensitivity) {
setDemodulatorLock(modemcf_get_demodulator_evm(mod) <= sensitivity);
}
void ModemDigital::digitalStart(ModemKitDigital * /* kit */, modemcf /* mod */, ModemIQData *input) {
size_t bufSize = input->data.size();
if (demodOutputDataDigital.size() != bufSize) {
if (demodOutputDataDigital.capacity() < bufSize) {
demodOutputDataDigital.reserve(bufSize);
}
demodOutputDataDigital.resize(bufSize);
}
}
void ModemDigital::digitalFinish(ModemKitDigital * /* kit */, modemcf /* mod */) {
#if ENABLE_DIGITAL_LAB
if (digitalOut && outStream.str().length()) {
digitalOut->write(outStream.str());
outStream.str("");
} else {
outStream.str("");
}
#endif
}
#if ENABLE_DIGITAL_LAB
void ModemDigital::setOutput(ModemDigitalOutput *modemDigitalOutput) {
digitalOut = modemDigitalOutput;
}
#endif

View File

@ -0,0 +1,64 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "Modem.h"
#include <map>
#include <vector>
#include <sstream>
#include <ostream>
#include <mutex>
class ModemKitDigital : public ModemKit {
public:
ModemKitDigital() : ModemKit() {
};
};
class ModemDigitalOutput {
public:
ModemDigitalOutput();
virtual ~ModemDigitalOutput();
virtual void write(std::string outp) = 0;
virtual void write(char outc) = 0;
virtual void Show() = 0;
virtual void Hide() = 0;
virtual void Close() = 0;
private:
};
class ModemDigital : public Modem {
public:
ModemDigital();
std::string getType() override;
int checkSampleRate(long long sampleRate, int audioSampleRate) override;
ModemKit *buildKit(long long sampleRate, int audioSampleRate) override;
void disposeKit(ModemKit *kit) override;
virtual void digitalStart(ModemKitDigital *kit, modemcf mod, ModemIQData *input);
virtual void digitalFinish(ModemKitDigital *kit, modemcf mod);
virtual void setDemodulatorLock(bool demod_lock_in);
virtual int getDemodulatorLock();
virtual void updateDemodulatorLock(modemcf mod, float sensitivity);
#if ENABLE_DIGITAL_LAB
void setOutput(ModemDigitalOutput *digitalOutput);
#endif
protected:
std::vector<unsigned int> demodOutputDataDigital;
std::atomic_bool currentDemodLock;
#if ENABLE_DIGITAL_LAB
ModemDigitalOutput *digitalOut;
std::stringstream outStream;
#endif
};

View File

@ -0,0 +1,50 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "ModemAM.h"
ModemAM::ModemAM() : ModemAnalog() {
// Create a DC blocker using 25 samples wide window
// and 30dB reduction of the DC level.
mDCBlock = firfilt_rrrf_create_dc_blocker (25,30.0f);
useSignalOutput(true);
}
ModemAM::~ModemAM() {
firfilt_rrrf_destroy(mDCBlock);
}
ModemBase *ModemAM::factory() {
return new ModemAM;
}
std::string ModemAM::getName() {
return "AM";
}
int ModemAM::getDefaultSampleRate() {
return 6000;
}
void ModemAM::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput* audioOut) {
auto *amkit = (ModemKitAnalog *)kit;
initOutputBuffers(amkit,input);
if (!bufSize) {
return;
}
// Implement an AM demodulator. Compute signal
// amplitude followed by a DC blocker to remove
// the DC offset.
for (size_t i = 0; i < bufSize; i++) {
float I = input->data[i].real;
float Q = input->data[i].imag;
firfilt_rrrf_push (mDCBlock,sqrt(I*I+Q*Q));
firfilt_rrrf_execute (mDCBlock,&demodOutputData[i]);
}
buildAudioOutput(amkit,audioOut,true);
}

View File

@ -0,0 +1,23 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "Modem.h"
#include "ModemAnalog.h"
class ModemAM : public ModemAnalog {
public:
ModemAM();
~ModemAM() override;
std::string getName() override;
static ModemBase *factory();
int getDefaultSampleRate() override;
void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) override;
private:
firfilt_rrrf mDCBlock;
};

View File

@ -0,0 +1,209 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "ModemCW.h"
// We are given a baseband segment BW (default 500Hz) wide which we want to
// offset by mBeepFrequency (default 650Hz). This yields a spectrum.
//
// | |....|....|
// | |....|....|
// | |....|....|
// -----------|---|----|----|--
// 0 150 650 1150
//
ModemCW::ModemCW()
: ModemAnalog(),
mBeepFrequency(650.0),
mGain(15.0),
mAutoGain(true),
mLO(nullptr),
mToReal(nullptr) {
mLO = nco_crcf_create(LIQUID_NCO);
mToReal = firhilbf_create(5, 60.0f);
useSignalOutput(true);
}
ModemCW::~ModemCW() {
if (mLO)
nco_crcf_destroy(mLO);
if (mToReal)
firhilbf_destroy(mToReal);
}
ModemArgInfoList ModemCW::getSettings() {
ModemArgInfoList args;
ModemArgInfo offsetArg;
offsetArg.key = "offset";
offsetArg.name = "Frequency Offset";
offsetArg.value = std::to_string(mBeepFrequency);
offsetArg.units = "Hz";
offsetArg.description = "Frequency Offset / Beep frequency (200-1000Hz)";
offsetArg.type = ModemArgInfo::Type::FLOAT;
offsetArg.range = ModemRange(200.0, 1000.0);
args.push_back(offsetArg);
ModemArgInfo autoGain;
autoGain.key = "auto";
autoGain.name = "Auto Gain";
autoGain.value = "on";
autoGain.type = ModemArgInfo::Type::STRING;
std::vector<std::string> autoOpts;
autoOpts.push_back("on");
autoOpts.push_back("off");
autoGain.optionNames = autoOpts;
autoGain.options = autoOpts;
args.push_back(autoGain);
ModemArgInfo gain;
gain.key = "gain";
gain.name = "Audio Gain";
gain.value = "15";
gain.units = "dB";
gain.description = "Gain Setting (0-40dB)";
gain.range = ModemRange(0.0, 40.0);
gain.type = ModemArgInfo::Type::FLOAT;
args.push_back(gain);
return args;
}
void ModemCW::writeSetting(std::string setting, std::string value) {
if (setting == "offset") {
mBeepFrequency = std::stof(value);
rebuildKit();
} else if (setting == "auto") {
mAutoGain = (value == "on");
} else if (setting == "gain") {
mGain = std::stof(value);
}
}
std::string ModemCW::readSetting(std::string setting) {
if (setting == "offset") {
return std::to_string(mBeepFrequency);
} else if (setting == "auto") {
return (mAutoGain) ? "on" : "off";
} else if (setting == "gain") {
return std::to_string(mGain);
}
return "";
}
ModemBase *ModemCW::factory() {
return new ModemCW;
}
std::string ModemCW::getName() {
return "CW";
}
int ModemCW::checkSampleRate(long long srate, int /* arate */) {
if (srate < MIN_BANDWIDTH)
return MIN_BANDWIDTH;
return (int)srate;
}
int ModemCW::getDefaultSampleRate() {
return MIN_BANDWIDTH;
}
// The modem object is asked to make a "ModemKit" given the IQ sample rate
// and the audio sample rate. For the CW modem the IQ sample rate is small
// or narrow bandwidth. The demodulated sample rate must be fast enough to
// sample 200-1000Hz tones. If the IQ sample rate is less than 2000Hz then
// one doesn't have the bandwidth for these tones. So we need to interpolate
// the input IQ to audioOut, frequency shift, then pass the real part.
// Simple solution is just interpolate the IQ data to the audio sample rate.
ModemKit *ModemCW::buildKit(long long sampleRate, int audioSampleRate) {
auto *kit = new ModemKitCW();
float As = 60.0f;
double ratio = double(audioSampleRate) / double(sampleRate);
kit->sampleRate = sampleRate;
kit->audioSampleRate = audioSampleRate;
kit->audioResampleRatio = ratio;
kit->mInputResampler = msresamp_cccf_create((float)ratio, As);
return kit;
}
void ModemCW::disposeKit(ModemKit *kit) {
auto *cwkit = (ModemKitCW *) kit;
msresamp_cccf_destroy(cwkit->mInputResampler);
delete kit;
}
void ModemCW::initOutputBuffers(ModemKitAnalog *akit, ModemIQData *input) {
bufSize = input->data.size();
if (!bufSize) {
return;
}
double audio_resample_ratio = akit->audioResampleRatio;
size_t audio_out_size = (size_t) ceil((double) (bufSize) * audio_resample_ratio) + 512;
// Just make everything the audio out size
if (mInput.size() != audio_out_size) {
if (mInput.capacity() < audio_out_size) {
mInput.reserve(audio_out_size);
}
mInput.resize(audio_out_size);
}
}
void ModemCW::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) {
unsigned int outSize;
float lsb;
liquid_float_complex sig;
auto *cwkit = (ModemKitCW *) kit;
initOutputBuffers(cwkit, input);
if (!bufSize) {
return;
}
// Interpolate IQ samples to full audio band. We need to be able to
// sample at 2 times the desired beep frequency.
msresamp_cccf_execute(cwkit->mInputResampler, &input->data[0], (unsigned int)bufSize, &mInput[0], &outSize);
// Make the shoe fit.
if (demodOutputData.size() != outSize) {
demodOutputData.resize(outSize);
}
// Set the LO to the desired beep frequency.
nco_crcf_set_frequency(mLO, 2.0f * (float)M_PI * mBeepFrequency / kit->audioSampleRate);
// Mix up from base band by beep frequency. Extract real part
for (unsigned int i = 0; i < outSize; i++) {
nco_crcf_mix_up(mLO, mInput[i], &sig);
nco_crcf_step(mLO);
firhilbf_c2r_execute(mToReal, sig, &lsb, &demodOutputData[i]);
}
// Determine gain automagically (if desired)
if (mAutoGain) {
aOutputCeilMA = aOutputCeilMA + (aOutputCeil - aOutputCeilMA) * 0.025f;
aOutputCeilMAA = aOutputCeilMAA + (aOutputCeilMA - aOutputCeilMAA) * 0.025f;
aOutputCeil = 0;
for (size_t i = 0; i < outSize; i++) {
if (demodOutputData[i] > aOutputCeil) {
aOutputCeil = demodOutputData[i];
}
}
mGain = 10.0f * std::log10(0.5f / aOutputCeilMAA);
}
// Apply gain to demodulated output data
for (size_t i = 0; i < outSize; i++) {
demodOutputData[i] *= std::pow(10.0f, mGain / 10.0f);
}
audioOut->channels = 1;
audioOut->sampleRate = cwkit->audioSampleRate;
audioOut->data.assign(demodOutputData.begin(), demodOutputData.end());
}

View File

@ -0,0 +1,54 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "Modem.h"
#include "ModemAnalog.h"
class ModemKitCW : public ModemKitAnalog {
public:
ModemKitCW() : ModemKitAnalog() {
};
msresamp_cccf mInputResampler{};
};
class ModemCW : public ModemAnalog {
public:
ModemCW();
~ModemCW() override;
std::string getName() override;
static ModemBase *factory();
int checkSampleRate(long long srate, int arate) override;
ModemKit *buildKit(long long srate, int arate) override;
void disposeKit(ModemKit *kit) override;
void initOutputBuffers(ModemKitAnalog *akit, ModemIQData *input) override;
int getDefaultSampleRate() override;
void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) override;
ModemArgInfoList getSettings() override;
void writeSetting(std::string setting, std::string value) override;
std::string readSetting(std::string setting) override;
// No resampling required.
std::vector<float> *getResampledOutputData() override { return &demodOutputData; }
private:
float mBeepFrequency;
float mGain;
bool mAutoGain;
nco_crcf mLO;
firhilbf mToReal;
std::vector<liquid_float_complex> mInput;
};

View File

@ -0,0 +1,42 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "ModemDSB.h"
ModemDSB::ModemDSB() : ModemAnalog() {
demodAM_DSB = ampmodem_create(0.5, LIQUID_AMPMODEM_DSB, 1);
useSignalOutput(true);
}
ModemDSB::~ModemDSB() {
ampmodem_destroy(demodAM_DSB);
}
ModemBase *ModemDSB::factory() {
return new ModemDSB;
}
std::string ModemDSB::getName() {
return "DSB";
}
int ModemDSB::getDefaultSampleRate() {
return 5400;
}
void ModemDSB::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) {
auto *amkit = (ModemKitAnalog *)kit;
initOutputBuffers(amkit, input);
if (!bufSize) {
return;
}
for (size_t i = 0; i < bufSize; i++) {
ampmodem_demodulate(demodAM_DSB, input->data[i], &demodOutputData[i]);
}
buildAudioOutput(amkit, audioOut, true);
}

View File

@ -0,0 +1,23 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "Modem.h"
#include "ModemAnalog.h"
class ModemDSB : public ModemAnalog {
public:
ModemDSB();
~ModemDSB() override;
std::string getName() override;
static ModemBase *factory();
int getDefaultSampleRate() override;
void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) override;
private:
ampmodem demodAM_DSB;
};

View File

@ -0,0 +1,39 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "ModemFM.h"
ModemFM::ModemFM() : ModemAnalog() {
demodFM = freqdem_create(0.5);
}
ModemFM::~ModemFM() {
freqdem_destroy(demodFM);
}
ModemBase *ModemFM::factory() {
return new ModemFM;
}
std::string ModemFM::getName() {
return "FM";
}
int ModemFM::getDefaultSampleRate() {
return 200000;
}
void ModemFM::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) {
auto *fmkit = (ModemKitAnalog *)kit;
initOutputBuffers(fmkit, input);
if (!bufSize) {
return;
}
freqdem_demodulate_block(demodFM, &input->data[0], (int)bufSize, &demodOutputData[0]);
buildAudioOutput(fmkit, audioOut, false);
}

View File

@ -0,0 +1,23 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "Modem.h"
#include "ModemAnalog.h"
class ModemFM : public ModemAnalog {
public:
ModemFM();
~ModemFM() override;
std::string getName() override;
static ModemBase *factory();
int getDefaultSampleRate() override;
void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) override;
private:
freqdem demodFM;
};

View File

@ -0,0 +1,287 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "ModemFMStereo.h"
ModemFMStereo::ModemFMStereo() {
demodFM = freqdem_create(0.5);
_demph = 75;
}
ModemFMStereo::~ModemFMStereo() {
freqdem_destroy(demodFM);
}
std::string ModemFMStereo::getType() {
return "analog";
}
std::string ModemFMStereo::getName() {
return "FMS";
}
ModemBase *ModemFMStereo::factory() {
return new ModemFMStereo;
}
int ModemFMStereo::checkSampleRate(long long sampleRate, int /* audioSampleRate */) {
if (sampleRate < 100000) {
return 100000;
} else if (sampleRate < 1500) {
return 1500;
} else {
return (int)sampleRate;
}
}
int ModemFMStereo::getDefaultSampleRate() {
return 200000;
}
ModemArgInfoList ModemFMStereo::getSettings() {
ModemArgInfoList args;
ModemArgInfo demphArg;
demphArg.key = "demph";
demphArg.name = "De-emphasis";
demphArg.value = std::to_string(_demph);
demphArg.description = "FM Stereo De-Emphasis, typically 75us in US/Canada, 50us elsewhere.";
demphArg.type = ModemArgInfo::Type::STRING;
std::vector<std::string> demphOptNames;
demphOptNames.push_back("None");
demphOptNames.push_back("10us");
demphOptNames.push_back("25us");
demphOptNames.push_back("32us");
demphOptNames.push_back("50us");
demphOptNames.push_back("75us");
demphArg.optionNames = demphOptNames;
std::vector<std::string> demphOpts;
demphOpts.push_back("0");
demphOpts.push_back("10");
demphOpts.push_back("25");
demphOpts.push_back("32");
demphOpts.push_back("50");
demphOpts.push_back("75");
demphArg.options = demphOpts;
args.push_back(demphArg);
return args;
}
void ModemFMStereo::writeSetting(std::string setting, std::string value) {
if (setting == "demph") {
_demph = std::stoi(value);
rebuildKit();
}
}
std::string ModemFMStereo::readSetting(std::string setting) {
if (setting == "demph") {
return std::to_string(_demph);
}
return "";
}
ModemKit *ModemFMStereo::buildKit(long long sampleRate, int audioSampleRate) {
auto *kit = new ModemKitFMStereo;
kit->audioResampleRatio = double(audioSampleRate) / double(sampleRate);
kit->sampleRate = sampleRate;
kit->audioSampleRate = audioSampleRate;
float As = 60.0f; // stop-band attenuation [dB]
kit->audioResampler = msresamp_rrrf_create((float)kit->audioResampleRatio, As);
kit->stereoResampler = msresamp_rrrf_create((float)kit->audioResampleRatio, As);
// Stereo filters / shifters
float firStereoCutoff = 16000.0f / float(audioSampleRate);
// filter transition
float ft = 1000.0f / float(audioSampleRate);
// fractional timing offset
float mu = 0.0f;
if (firStereoCutoff < 0) {
firStereoCutoff = 0;
}
if (firStereoCutoff > 0.5) {
firStereoCutoff = 0.5;
}
unsigned int h_len = estimate_req_filter_len(ft, As);
float *h = new float[h_len];
liquid_firdes_kaiser(h_len, firStereoCutoff, As, mu, h);
kit->firStereoLeft = firfilt_rrrf_create(h, h_len);
kit->firStereoRight = firfilt_rrrf_create(h, h_len);
// stereo pilot filter
float bw = float(sampleRate);
if (bw < 100000.0) {
bw = 100000.0;
}
unsigned int order = 5; // filter order
float f0 = ((float) 19000 / bw);
float fc = ((float) 19500 / bw);
float Ap = 1.0f;
kit->iirStereoPilot = iirfilt_crcf_create_prototype(LIQUID_IIRDES_CHEBY2, LIQUID_IIRDES_BANDPASS, LIQUID_IIRDES_SOS, order, fc, f0, Ap, As);
kit->firStereoR2C = firhilbf_create(5, 60.0f);
kit->firStereoC2R = firhilbf_create(5, 60.0f);
kit->stereoPilot = nco_crcf_create(LIQUID_VCO);
nco_crcf_reset(kit->stereoPilot);
nco_crcf_pll_set_bandwidth(kit->stereoPilot, 0.25f);
kit->demph = _demph;
if (_demph) {
double f = (1.0 / (2.0 * M_PI * double(_demph) * 1e-6));
double t = 1.0 / (2.0 * M_PI * f);
t = 1.0 / (2.0 * double(audioSampleRate) * tan(1.0 / (2.0 * double(audioSampleRate) * t)));
double tb = (1.0 + 2.0 * t * double(audioSampleRate));
float b_demph[2] = { (float)(1.0 / tb), (float)(1.0 / tb) };
float a_demph[2] = { 1.0, (float)((1.0 - 2.0 * t * double(audioSampleRate)) / tb) };
kit->iirDemphL = iirfilt_rrrf_create(b_demph, 2, a_demph, 2);
kit->iirDemphR = iirfilt_rrrf_create(b_demph, 2, a_demph, 2);
} else {
kit->iirDemphL = nullptr;
kit->iirDemphR = nullptr;
kit->demph = 0;
}
return kit;
}
void ModemFMStereo::disposeKit(ModemKit *kit) {
auto *fmkit = (ModemKitFMStereo *)kit;
msresamp_rrrf_destroy(fmkit->audioResampler);
msresamp_rrrf_destroy(fmkit->stereoResampler);
firfilt_rrrf_destroy(fmkit->firStereoLeft);
firfilt_rrrf_destroy(fmkit->firStereoRight);
firhilbf_destroy(fmkit->firStereoR2C);
firhilbf_destroy(fmkit->firStereoC2R);
nco_crcf_destroy(fmkit->stereoPilot);
if (fmkit->iirDemphR) { iirfilt_rrrf_destroy(fmkit->iirDemphR); }
if (fmkit->iirDemphL) { iirfilt_rrrf_destroy(fmkit->iirDemphL); }
}
void ModemFMStereo::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) {
auto *fmkit = (ModemKitFMStereo *)kit;
size_t bufSize = input->data.size();
liquid_float_complex u, v, w, x, y;
double audio_resample_ratio = fmkit->audioResampleRatio;
if (demodOutputData.size() != bufSize) {
if (demodOutputData.capacity() < bufSize) {
demodOutputData.reserve(bufSize);
}
demodOutputData.resize(bufSize);
}
size_t audio_out_size = (size_t)ceil((double) (bufSize) * audio_resample_ratio) + 512;
freqdem_demodulate_block(demodFM, &input->data[0], (int)bufSize, &demodOutputData[0]);
if (resampledOutputData.size() != audio_out_size) {
if (resampledOutputData.capacity() < audio_out_size) {
resampledOutputData.reserve(audio_out_size);
}
resampledOutputData.resize(audio_out_size);
}
unsigned int numAudioWritten;
msresamp_rrrf_execute(fmkit->audioResampler, &demodOutputData[0], (int)bufSize, &resampledOutputData[0], &numAudioWritten);
if (demodStereoData.size() != bufSize) {
if (demodStereoData.capacity() < bufSize) {
demodStereoData.reserve(bufSize);
}
demodStereoData.resize(bufSize);
}
float phase_error = 0;
for (size_t i = 0; i < bufSize; i++) {
// real -> complex
firhilbf_r2c_execute(fmkit->firStereoR2C, demodOutputData[i], &x);
// 19khz pilot band-pass
iirfilt_crcf_execute(fmkit->iirStereoPilot, x, &v);
nco_crcf_cexpf(fmkit->stereoPilot, &w);
w.imag = -w.imag; // conjf(w)
// multiply u = v * conjf(w)
u.real = v.real * w.real - v.imag * w.imag;
u.imag = v.real * w.imag + v.imag * w.real;
// cargf(u)
phase_error = atan2f(u.imag,u.real);
// step pll
nco_crcf_pll_step(fmkit->stereoPilot, phase_error);
nco_crcf_step(fmkit->stereoPilot);
// 38khz down-mix
nco_crcf_mix_down(fmkit->stereoPilot, x, &y);
nco_crcf_mix_down(fmkit->stereoPilot, y, &x);
// complex -> real
float usb_discard;
firhilbf_c2r_execute(fmkit->firStereoC2R, x, &demodStereoData[i], &usb_discard);
}
// std::cout << "[PLL] phase error: " << phase_error;
// std::cout << " freq:" << (((nco_crcf_get_frequency(stereoPilot) / (2.0 * M_PI)) * inp->sampleRate)) << std::endl;
if (audio_out_size != resampledStereoData.size()) {
if (resampledStereoData.capacity() < audio_out_size) {
resampledStereoData.reserve(audio_out_size);
}
resampledStereoData.resize(audio_out_size);
}
msresamp_rrrf_execute(fmkit->stereoResampler, &demodStereoData[0], (int)bufSize, &resampledStereoData[0], &numAudioWritten);
audioOut->channels = 2;
if (audioOut->data.capacity() < (numAudioWritten * 2)) {
audioOut->data.reserve(numAudioWritten * 2);
}
audioOut->data.resize(numAudioWritten * 2);
for (size_t i = 0; i < numAudioWritten; i++) {
float l, r;
float ld, rd;
if (fmkit->demph) {
iirfilt_rrrf_execute(fmkit->iirDemphL, 0.568f * (resampledOutputData[i] - (resampledStereoData[i])), &ld);
iirfilt_rrrf_execute(fmkit->iirDemphR, 0.568f * (resampledOutputData[i] + (resampledStereoData[i])), &rd);
firfilt_rrrf_push(fmkit->firStereoLeft, ld);
firfilt_rrrf_execute(fmkit->firStereoLeft, &l);
firfilt_rrrf_push(fmkit->firStereoRight, rd);
firfilt_rrrf_execute(fmkit->firStereoRight, &r);
} else {
firfilt_rrrf_push(fmkit->firStereoLeft, 0.568f * (resampledOutputData[i] - (resampledStereoData[i])));
firfilt_rrrf_execute(fmkit->firStereoLeft, &l);
firfilt_rrrf_push(fmkit->firStereoRight, 0.568f * (resampledOutputData[i] + (resampledStereoData[i])));
firfilt_rrrf_execute(fmkit->firStereoRight, &r);
}
audioOut->data[i * 2] = l;
audioOut->data[i * 2 + 1] = r;
}
}

View File

@ -0,0 +1,62 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "Modem.h"
class ModemKitFMStereo: public ModemKit {
public:
ModemKitFMStereo() : audioResampler(nullptr), stereoResampler(nullptr), audioResampleRatio(0), firStereoLeft(nullptr), firStereoRight(nullptr), iirStereoPilot(nullptr),
demph(0), iirDemphR(nullptr), iirDemphL(nullptr), firStereoR2C(nullptr), firStereoC2R(nullptr), stereoPilot(nullptr) {
}
msresamp_rrrf audioResampler;
msresamp_rrrf stereoResampler;
double audioResampleRatio;
firfilt_rrrf firStereoLeft;
firfilt_rrrf firStereoRight;
iirfilt_crcf iirStereoPilot;
int demph;
iirfilt_rrrf iirDemphR;
iirfilt_rrrf iirDemphL;
firhilbf firStereoR2C;
firhilbf firStereoC2R;
nco_crcf stereoPilot;
};
class ModemFMStereo : public Modem {
public:
ModemFMStereo();
~ModemFMStereo() override;
std::string getType() override;
std::string getName() override;
static ModemBase *factory();
int checkSampleRate(long long sampleRate, int audioSampleRate) override;
int getDefaultSampleRate() override;
ModemArgInfoList getSettings() override;
void writeSetting(std::string setting, std::string value) override;
std::string readSetting(std::string setting) override;
ModemKit *buildKit(long long sampleRate, int audioSampleRate) override;
void disposeKit(ModemKit *kit) override;
void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) override;
private:
std::vector<float> demodOutputData;
std::vector<float> demodStereoData;
std::vector<float> resampledOutputData;
std::vector<float> resampledStereoData;
freqdem demodFM;
int _demph;
};

View File

@ -0,0 +1,57 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "ModemIQ.h"
ModemIQ::ModemIQ() = default;
std::string ModemIQ::getType() {
return "analog";
}
std::string ModemIQ::getName() {
return "I/Q";
}
ModemBase *ModemIQ::factory() {
return new ModemIQ;
}
ModemKit *ModemIQ::buildKit(long long sampleRate, int audioSampleRate) {
auto *kit = new ModemKit;
kit->sampleRate = sampleRate;
kit->audioSampleRate = audioSampleRate;
return kit;
}
void ModemIQ::disposeKit(ModemKit *kit) {
delete kit;
}
int ModemIQ::checkSampleRate(long long /* sampleRate */, int audioSampleRate) {
return audioSampleRate;
}
int ModemIQ::getDefaultSampleRate() {
return 48000;
}
void ModemIQ::demodulate(ModemKit * /* kit */, ModemIQData *input, AudioThreadInput *audioOut) {
size_t bufSize = input->data.size();
if (!bufSize) {
return;
}
audioOut->channels = 2;
if (audioOut->data.capacity() < (bufSize * 2)) {
audioOut->data.reserve(bufSize * 2);
}
audioOut->data.resize(bufSize * 2);
for (size_t i = 0; i < bufSize; i++) {
audioOut->data[i * 2] = input->data[i].imag;
audioOut->data[i * 2 + 1] = input->data[i].real;
}
}

View File

@ -0,0 +1,27 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "Modem.h"
class ModemIQ : public Modem {
public:
ModemIQ();
std::string getType() override;
std::string getName() override;
static ModemBase *factory();
int checkSampleRate(long long sampleRate, int audioSampleRate) override;
int getDefaultSampleRate() override;
ModemKit *buildKit(long long sampleRate, int audioSampleRate) override;
void disposeKit(ModemKit *kit) override;
void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) override;
private:
};

View File

@ -0,0 +1,64 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "ModemLSB.h"
ModemLSB::ModemLSB() : ModemAnalog() {
// half band filter used for side-band elimination
ssbFilt = iirfilt_crcf_create_lowpass(6, 0.25);
ssbShift = nco_crcf_create(LIQUID_NCO);
nco_crcf_set_frequency(ssbShift, (float)((2.0 * M_PI) * 0.25));
c2rFilt = firhilbf_create(5, 90.0);
useSignalOutput(true);
}
ModemBase *ModemLSB::factory() {
return new ModemLSB;
}
std::string ModemLSB::getName() {
return "LSB";
}
ModemLSB::~ModemLSB() {
iirfilt_crcf_destroy(ssbFilt);
nco_crcf_destroy(ssbShift);
firhilbf_destroy(c2rFilt);
}
int ModemLSB::checkSampleRate(long long sampleRate, int /* audioSampleRate */) {
if (sampleRate < MIN_BANDWIDTH) {
return MIN_BANDWIDTH;
}
if (sampleRate % 2 == 0) {
return (int)sampleRate;
}
return (int)(sampleRate+1);
}
int ModemLSB::getDefaultSampleRate() {
return 5400;
}
void ModemLSB::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) {
auto *akit = (ModemKitAnalog *)kit;
initOutputBuffers(akit,input);
if (!bufSize) {
return;
}
liquid_float_complex x, y;
for (size_t i = 0; i < bufSize; i++) { // Reject upper band
nco_crcf_step(ssbShift);
nco_crcf_mix_up(ssbShift, input->data[i], &x);
iirfilt_crcf_execute(ssbFilt, x, &y);
nco_crcf_mix_down(ssbShift, y, &x);
float usb_discard;
firhilbf_c2r_execute(c2rFilt, x, &demodOutputData[i], &usb_discard);
}
buildAudioOutput(akit, audioOut, true);
}

View File

@ -0,0 +1,25 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "ModemAnalog.h"
class ModemLSB : public ModemAnalog {
public:
ModemLSB();
~ModemLSB() override;
std::string getName() override;
static ModemBase *factory();
int checkSampleRate(long long sampleRate, int audioSampleRate) override;
int getDefaultSampleRate() override;
void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) override;
private:
iirfilt_crcf ssbFilt;
firhilbf c2rFilt;
nco_crcf ssbShift;
};

View File

@ -0,0 +1,39 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "ModemNBFM.h"
ModemNBFM::ModemNBFM() : ModemAnalog() {
demodFM = freqdem_create(0.5);
}
ModemNBFM::~ModemNBFM() {
freqdem_destroy(demodFM);
}
ModemBase *ModemNBFM::factory() {
return new ModemNBFM;
}
std::string ModemNBFM::getName() {
return "NBFM";
}
int ModemNBFM::getDefaultSampleRate() {
return 12500;
}
void ModemNBFM::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) {
auto *fmkit = (ModemKitAnalog *)kit;
initOutputBuffers(fmkit, input);
if (!bufSize) {
return;
}
freqdem_demodulate_block(demodFM, &input->data[0], (unsigned int)bufSize, &demodOutputData[0]);
buildAudioOutput(fmkit, audioOut, false);
}

View File

@ -0,0 +1,23 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#pragma once
#include "Modem.h"
#include "ModemAnalog.h"
class ModemNBFM : public ModemAnalog {
public:
ModemNBFM();
~ModemNBFM() override;
std::string getName() override;
static ModemBase *factory();
int getDefaultSampleRate() override;
void demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) override;
private:
freqdem demodFM;
};

View File

@ -0,0 +1,65 @@
// Copyright (c) Charles J. Cliffe
// SPDX-License-Identifier: GPL-2.0+
#include "ModemUSB.h"
ModemUSB::ModemUSB() : ModemAnalog() {
// half band filter used for side-band elimination
ssbFilt = iirfilt_crcf_create_lowpass(6, 0.25);
ssbShift = nco_crcf_create(LIQUID_NCO);
nco_crcf_set_frequency(ssbShift, (float)((2.0 * M_PI) * 0.25));
c2rFilt = firhilbf_create(5, 90.0);
useSignalOutput(true);
}
ModemBase *ModemUSB::factory() {
return new ModemUSB;
}
std::string ModemUSB::getName() {
return "USB";
}
ModemUSB::~ModemUSB() {
iirfilt_crcf_destroy(ssbFilt);
nco_crcf_destroy(ssbShift);
firhilbf_destroy(c2rFilt);
}
int ModemUSB::checkSampleRate(long long sampleRate, int /* audioSampleRate */) {
if (sampleRate < MIN_BANDWIDTH) {
return MIN_BANDWIDTH;
}
if (sampleRate % 2 == 0) {
return (int)sampleRate;
}
return (int)(sampleRate+1);
}
int ModemUSB::getDefaultSampleRate() {
return 5400;
}
void ModemUSB::demodulate(ModemKit *kit, ModemIQData *input, AudioThreadInput *audioOut) {
auto *akit = (ModemKitAnalog *)kit;
initOutputBuffers(akit,input);
if (!bufSize) {
return;
}
liquid_float_complex x, y;
for (size_t i = 0; i < bufSize; i++) { // Reject lower band
nco_crcf_step(ssbShift);
nco_crcf_mix_down(ssbShift, input->data[i], &x);
iirfilt_crcf_execute(ssbFilt, x, &y);
nco_crcf_mix_up(ssbShift, y, &x);
float lsb_discard;
firhilbf_c2r_execute(c2rFilt, x, &lsb_discard, &demodOutputData[i]);
}
buildAudioOutput(akit, audioOut, true);
}

Some files were not shown because too many files have changed in this diff Show More