Add software
This commit is contained in:
973
Software/CubicSDR/src/AppConfig.cpp
Normal file
973
Software/CubicSDR/src/AppConfig.cpp
Normal 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
|
||||
217
Software/CubicSDR/src/AppConfig.h
Normal file
217
Software/CubicSDR/src/AppConfig.h
Normal 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
|
||||
};
|
||||
3202
Software/CubicSDR/src/AppFrame.cpp
Normal file
3202
Software/CubicSDR/src/AppFrame.cpp
Normal file
File diff suppressed because it is too large
Load Diff
389
Software/CubicSDR/src/AppFrame.h
Normal file
389
Software/CubicSDR/src/AppFrame.h
Normal 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
|
||||
677
Software/CubicSDR/src/BookmarkMgr.cpp
Normal file
677
Software/CubicSDR/src/BookmarkMgr.cpp
Normal 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);
|
||||
}
|
||||
|
||||
137
Software/CubicSDR/src/BookmarkMgr.h
Normal file
137
Software/CubicSDR/src/BookmarkMgr.h
Normal 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;
|
||||
};
|
||||
1203
Software/CubicSDR/src/CubicSDR.cpp
Normal file
1203
Software/CubicSDR/src/CubicSDR.cpp
Normal file
File diff suppressed because it is too large
Load Diff
270
Software/CubicSDR/src/CubicSDR.h
Normal file
270
Software/CubicSDR/src/CubicSDR.h
Normal 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)
|
||||
BIN
Software/CubicSDR/src/CubicSDR.png
Normal file
BIN
Software/CubicSDR/src/CubicSDR.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
518
Software/CubicSDR/src/CubicSDR.xpm
Normal file
518
Software/CubicSDR/src/CubicSDR.xpm
Normal 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"
|
||||
};
|
||||
73
Software/CubicSDR/src/CubicSDRDefs.h
Normal file
73
Software/CubicSDR/src/CubicSDRDefs.h
Normal 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
|
||||
107
Software/CubicSDR/src/DemodLabelDialog.cpp
Normal file
107
Software/CubicSDR/src/DemodLabelDialog.cpp
Normal 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();
|
||||
}
|
||||
31
Software/CubicSDR/src/DemodLabelDialog.h
Normal file
31
Software/CubicSDR/src/DemodLabelDialog.h
Normal 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()
|
||||
};
|
||||
266
Software/CubicSDR/src/FrequencyDialog.cpp
Normal file
266
Software/CubicSDR/src/FrequencyDialog.cpp
Normal 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();
|
||||
}
|
||||
43
Software/CubicSDR/src/FrequencyDialog.h
Normal file
43
Software/CubicSDR/src/FrequencyDialog.h
Normal 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()
|
||||
};
|
||||
132
Software/CubicSDR/src/IOThread.cpp
Normal file
132
Software/CubicSDR/src/IOThread.cpp
Normal 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();
|
||||
}
|
||||
209
Software/CubicSDR/src/IOThread.h
Normal file
209
Software/CubicSDR/src/IOThread.h
Normal 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;
|
||||
|
||||
|
||||
};
|
||||
299
Software/CubicSDR/src/ModemProperties.cpp
Normal file
299
Software/CubicSDR/src/ModemProperties.cpp
Normal 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();
|
||||
}
|
||||
61
Software/CubicSDR/src/ModemProperties.h
Normal file
61
Software/CubicSDR/src/ModemProperties.h
Normal 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;
|
||||
};
|
||||
195
Software/CubicSDR/src/SessionMgr.cpp
Normal file
195
Software/CubicSDR/src/SessionMgr.cpp
Normal 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;
|
||||
}
|
||||
14
Software/CubicSDR/src/SessionMgr.h
Normal file
14
Software/CubicSDR/src/SessionMgr.h
Normal 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);
|
||||
};
|
||||
46
Software/CubicSDR/src/audio/AudioFile.cpp
Normal file
46
Software/CubicSDR/src/audio/AudioFile.cpp
Normal 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();
|
||||
}
|
||||
|
||||
25
Software/CubicSDR/src/audio/AudioFile.h
Normal file
25
Software/CubicSDR/src/audio/AudioFile.h
Normal 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;
|
||||
|
||||
};
|
||||
219
Software/CubicSDR/src/audio/AudioFileWAV.cpp
Normal file
219
Software/CubicSDR/src/audio/AudioFileWAV.cpp
Normal 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();
|
||||
}
|
||||
42
Software/CubicSDR/src/audio/AudioFileWAV.h
Normal file
42
Software/CubicSDR/src/audio/AudioFileWAV.h
Normal 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);
|
||||
};
|
||||
140
Software/CubicSDR/src/audio/AudioSinkFileThread.cpp
Normal file
140
Software/CubicSDR/src/audio/AudioSinkFileThread.cpp
Normal 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(<m, "%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", <m);
|
||||
|
||||
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(<m, "%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", <m);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
50
Software/CubicSDR/src/audio/AudioSinkFileThread.h
Normal file
50
Software/CubicSDR/src/audio/AudioSinkFileThread.h
Normal 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;
|
||||
|
||||
};
|
||||
|
||||
52
Software/CubicSDR/src/audio/AudioSinkThread.cpp
Normal file
52
Software/CubicSDR/src/audio/AudioSinkThread.cpp
Normal 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();
|
||||
}
|
||||
25
Software/CubicSDR/src/audio/AudioSinkThread.h
Normal file
25
Software/CubicSDR/src/audio/AudioSinkThread.h
Normal 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;
|
||||
|
||||
};
|
||||
604
Software/CubicSDR/src/audio/AudioThread.cpp
Normal file
604
Software/CubicSDR/src/audio/AudioThread.cpp
Normal 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(¶meters, 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(¶meters, 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;
|
||||
}
|
||||
151
Software/CubicSDR/src/audio/AudioThread.h
Normal file
151
Software/CubicSDR/src/audio/AudioThread.h
Normal 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;
|
||||
};
|
||||
68
Software/CubicSDR/src/demod/DemodDefs.h
Normal file
68
Software/CubicSDR/src/demod/DemodDefs.h
Normal 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;
|
||||
689
Software/CubicSDR/src/demod/DemodulatorInstance.cpp
Normal file
689
Software/CubicSDR/src/demod/DemodulatorInstance.cpp
Normal 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
|
||||
178
Software/CubicSDR/src/demod/DemodulatorInstance.h
Normal file
178
Software/CubicSDR/src/demod/DemodulatorInstance.h
Normal 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;
|
||||
588
Software/CubicSDR/src/demod/DemodulatorMgr.cpp
Normal file
588
Software/CubicSDR/src/demod/DemodulatorMgr.cpp
Normal 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;
|
||||
}
|
||||
|
||||
109
Software/CubicSDR/src/demod/DemodulatorMgr.h
Normal file
109
Software/CubicSDR/src/demod/DemodulatorMgr.h
Normal 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;
|
||||
};
|
||||
402
Software/CubicSDR/src/demod/DemodulatorPreThread.cpp
Normal file
402
Software/CubicSDR/src/demod/DemodulatorPreThread.cpp
Normal 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);
|
||||
}
|
||||
89
Software/CubicSDR/src/demod/DemodulatorPreThread.h
Normal file
89
Software/CubicSDR/src/demod/DemodulatorPreThread.h
Normal 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;
|
||||
};
|
||||
414
Software/CubicSDR/src/demod/DemodulatorThread.cpp
Normal file
414
Software/CubicSDR/src/demod/DemodulatorThread.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
75
Software/CubicSDR/src/demod/DemodulatorThread.h
Normal file
75
Software/CubicSDR/src/demod/DemodulatorThread.h
Normal 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;
|
||||
};
|
||||
119
Software/CubicSDR/src/demod/DemodulatorWorkerThread.cpp
Normal file
119
Software/CubicSDR/src/demod/DemodulatorWorkerThread.cpp
Normal 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();
|
||||
}
|
||||
102
Software/CubicSDR/src/demod/DemodulatorWorkerThread.h
Normal file
102
Software/CubicSDR/src/demod/DemodulatorWorkerThread.h
Normal 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;
|
||||
};
|
||||
143
Software/CubicSDR/src/forms/Bookmark/BookmarkPanel.cpp
Normal file
143
Software/CubicSDR/src/forms/Bookmark/BookmarkPanel.cpp
Normal 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 ) );
|
||||
|
||||
}
|
||||
968
Software/CubicSDR/src/forms/Bookmark/BookmarkPanel.fbp
Normal file
968
Software/CubicSDR/src/forms/Bookmark/BookmarkPanel.fbp
Normal 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>
|
||||
86
Software/CubicSDR/src/forms/Bookmark/BookmarkPanel.h
Normal file
86
Software/CubicSDR/src/forms/Bookmark/BookmarkPanel.h
Normal 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();
|
||||
|
||||
};
|
||||
|
||||
1637
Software/CubicSDR/src/forms/Bookmark/BookmarkView.cpp
Normal file
1637
Software/CubicSDR/src/forms/Bookmark/BookmarkView.cpp
Normal file
File diff suppressed because it is too large
Load Diff
203
Software/CubicSDR/src/forms/Bookmark/BookmarkView.h
Normal file
203
Software/CubicSDR/src/forms/Bookmark/BookmarkView.h
Normal 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);
|
||||
|
||||
};
|
||||
9
Software/CubicSDR/src/forms/Dialog/AboutDialog.cpp
Normal file
9
Software/CubicSDR/src/forms/Dialog/AboutDialog.cpp
Normal 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);
|
||||
}
|
||||
9002
Software/CubicSDR/src/forms/Dialog/AboutDialog.fbp
Normal file
9002
Software/CubicSDR/src/forms/Dialog/AboutDialog.fbp
Normal file
File diff suppressed because it is too large
Load Diff
15
Software/CubicSDR/src/forms/Dialog/AboutDialog.h
Normal file
15
Software/CubicSDR/src/forms/Dialog/AboutDialog.h
Normal 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 );
|
||||
|
||||
};
|
||||
700
Software/CubicSDR/src/forms/Dialog/AboutDialogBase.cpp
Normal file
700
Software/CubicSDR/src/forms/Dialog/AboutDialogBase.cpp
Normal 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()
|
||||
{
|
||||
}
|
||||
194
Software/CubicSDR/src/forms/Dialog/AboutDialogBase.h
Normal file
194
Software/CubicSDR/src/forms/Dialog/AboutDialogBase.h
Normal 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();
|
||||
|
||||
};
|
||||
|
||||
62
Software/CubicSDR/src/forms/Dialog/ActionDialog.cpp
Normal file
62
Software/CubicSDR/src/forms/Dialog/ActionDialog.cpp
Normal 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() {
|
||||
|
||||
}
|
||||
24
Software/CubicSDR/src/forms/Dialog/ActionDialog.h
Normal file
24
Software/CubicSDR/src/forms/Dialog/ActionDialog.h
Normal 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;
|
||||
};
|
||||
53
Software/CubicSDR/src/forms/Dialog/ActionDialogBase.cpp
Normal file
53
Software/CubicSDR/src/forms/Dialog/ActionDialogBase.cpp
Normal 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 );
|
||||
|
||||
}
|
||||
421
Software/CubicSDR/src/forms/Dialog/ActionDialogBase.fbp
Normal file
421
Software/CubicSDR/src/forms/Dialog/ActionDialogBase.fbp
Normal 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>
|
||||
53
Software/CubicSDR/src/forms/Dialog/ActionDialogBase.h
Normal file
53
Software/CubicSDR/src/forms/Dialog/ActionDialogBase.h
Normal 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__
|
||||
51
Software/CubicSDR/src/forms/Dialog/PortSelectorDialog.cpp
Normal file
51
Software/CubicSDR/src/forms/Dialog/PortSelectorDialog.cpp
Normal 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();
|
||||
}
|
||||
493
Software/CubicSDR/src/forms/Dialog/PortSelectorDialog.fbp
Normal file
493
Software/CubicSDR/src/forms/Dialog/PortSelectorDialog.fbp
Normal 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>
|
||||
12
Software/CubicSDR/src/forms/Dialog/PortSelectorDialog.h
Normal file
12
Software/CubicSDR/src/forms/Dialog/PortSelectorDialog.h
Normal 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;
|
||||
};
|
||||
@@ -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 );
|
||||
|
||||
}
|
||||
58
Software/CubicSDR/src/forms/Dialog/PortSelectorDialogBase.h
Normal file
58
Software/CubicSDR/src/forms/Dialog/PortSelectorDialogBase.h
Normal 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();
|
||||
|
||||
};
|
||||
|
||||
139
Software/CubicSDR/src/forms/DigitalConsole/DigitalConsole.cpp
Normal file
139
Software/CubicSDR/src/forms/DigitalConsole/DigitalConsole.cpp
Normal 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);
|
||||
}
|
||||
485
Software/CubicSDR/src/forms/DigitalConsole/DigitalConsole.fbp
Normal file
485
Software/CubicSDR/src/forms/DigitalConsole/DigitalConsole.fbp
Normal 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>
|
||||
64
Software/CubicSDR/src/forms/DigitalConsole/DigitalConsole.h
Normal file
64
Software/CubicSDR/src/forms/DigitalConsole/DigitalConsole.h
Normal 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;
|
||||
};
|
||||
|
||||
@@ -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 ) );
|
||||
|
||||
}
|
||||
@@ -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__
|
||||
61
Software/CubicSDR/src/forms/SDRDevices/SDRDeviceAdd.cpp
Normal file
61
Software/CubicSDR/src/forms/SDRDevices/SDRDeviceAdd.cpp
Normal 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;
|
||||
}
|
||||
681
Software/CubicSDR/src/forms/SDRDevices/SDRDeviceAdd.fbp
Normal file
681
Software/CubicSDR/src/forms/SDRDevices/SDRDeviceAdd.fbp
Normal 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. 

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"><Parameter></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>
|
||||
24
Software/CubicSDR/src/forms/SDRDevices/SDRDeviceAdd.h
Normal file
24
Software/CubicSDR/src/forms/SDRDevices/SDRDeviceAdd.h
Normal 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;
|
||||
};
|
||||
83
Software/CubicSDR/src/forms/SDRDevices/SDRDeviceAddForm.cpp
Normal file
83
Software/CubicSDR/src/forms/SDRDevices/SDRDeviceAddForm.cpp
Normal 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 );
|
||||
|
||||
}
|
||||
59
Software/CubicSDR/src/forms/SDRDevices/SDRDeviceAddForm.h
Normal file
59
Software/CubicSDR/src/forms/SDRDevices/SDRDeviceAddForm.h
Normal 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__
|
||||
628
Software/CubicSDR/src/forms/SDRDevices/SDRDevices.cpp
Normal file
628
Software/CubicSDR/src/forms/SDRDevices/SDRDevices.cpp
Normal 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");
|
||||
}
|
||||
1033
Software/CubicSDR/src/forms/SDRDevices/SDRDevices.fbp
Normal file
1033
Software/CubicSDR/src/forms/SDRDevices/SDRDevices.fbp
Normal file
File diff suppressed because it is too large
Load Diff
55
Software/CubicSDR/src/forms/SDRDevices/SDRDevices.h
Normal file
55
Software/CubicSDR/src/forms/SDRDevices/SDRDevices.h
Normal 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;
|
||||
};
|
||||
115
Software/CubicSDR/src/forms/SDRDevices/SDRDevicesForm.cpp
Normal file
115
Software/CubicSDR/src/forms/SDRDevices/SDRDevicesForm.cpp
Normal 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 ) );
|
||||
|
||||
}
|
||||
77
Software/CubicSDR/src/forms/SDRDevices/SDRDevicesForm.h
Normal file
77
Software/CubicSDR/src/forms/SDRDevices/SDRDevicesForm.h
Normal 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__
|
||||
117
Software/CubicSDR/src/modules/modem/Modem.cpp
Normal file
117
Software/CubicSDR/src/modules/modem/Modem.cpp
Normal 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);
|
||||
}
|
||||
166
Software/CubicSDR/src/modules/modem/Modem.h
Normal file
166
Software/CubicSDR/src/modules/modem/Modem.h
Normal 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;
|
||||
};
|
||||
101
Software/CubicSDR/src/modules/modem/ModemAnalog.cpp
Normal file
101
Software/CubicSDR/src/modules/modem/ModemAnalog.cpp
Normal 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;
|
||||
}
|
||||
37
Software/CubicSDR/src/modules/modem/ModemAnalog.h
Normal file
37
Software/CubicSDR/src/modules/modem/ModemAnalog.h
Normal 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;
|
||||
};
|
||||
81
Software/CubicSDR/src/modules/modem/ModemDigital.cpp
Normal file
81
Software/CubicSDR/src/modules/modem/ModemDigital.cpp
Normal 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
|
||||
64
Software/CubicSDR/src/modules/modem/ModemDigital.h
Normal file
64
Software/CubicSDR/src/modules/modem/ModemDigital.h
Normal 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
|
||||
};
|
||||
50
Software/CubicSDR/src/modules/modem/analog/ModemAM.cpp
Normal file
50
Software/CubicSDR/src/modules/modem/analog/ModemAM.cpp
Normal 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);
|
||||
}
|
||||
23
Software/CubicSDR/src/modules/modem/analog/ModemAM.h
Normal file
23
Software/CubicSDR/src/modules/modem/analog/ModemAM.h
Normal 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;
|
||||
};
|
||||
209
Software/CubicSDR/src/modules/modem/analog/ModemCW.cpp
Normal file
209
Software/CubicSDR/src/modules/modem/analog/ModemCW.cpp
Normal 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());
|
||||
}
|
||||
54
Software/CubicSDR/src/modules/modem/analog/ModemCW.h
Normal file
54
Software/CubicSDR/src/modules/modem/analog/ModemCW.h
Normal 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;
|
||||
};
|
||||
42
Software/CubicSDR/src/modules/modem/analog/ModemDSB.cpp
Normal file
42
Software/CubicSDR/src/modules/modem/analog/ModemDSB.cpp
Normal 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);
|
||||
}
|
||||
23
Software/CubicSDR/src/modules/modem/analog/ModemDSB.h
Normal file
23
Software/CubicSDR/src/modules/modem/analog/ModemDSB.h
Normal 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;
|
||||
};
|
||||
39
Software/CubicSDR/src/modules/modem/analog/ModemFM.cpp
Normal file
39
Software/CubicSDR/src/modules/modem/analog/ModemFM.cpp
Normal 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);
|
||||
}
|
||||
23
Software/CubicSDR/src/modules/modem/analog/ModemFM.h
Normal file
23
Software/CubicSDR/src/modules/modem/analog/ModemFM.h
Normal 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;
|
||||
};
|
||||
287
Software/CubicSDR/src/modules/modem/analog/ModemFMStereo.cpp
Normal file
287
Software/CubicSDR/src/modules/modem/analog/ModemFMStereo.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
62
Software/CubicSDR/src/modules/modem/analog/ModemFMStereo.h
Normal file
62
Software/CubicSDR/src/modules/modem/analog/ModemFMStereo.h
Normal 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;
|
||||
};
|
||||
57
Software/CubicSDR/src/modules/modem/analog/ModemIQ.cpp
Normal file
57
Software/CubicSDR/src/modules/modem/analog/ModemIQ.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
27
Software/CubicSDR/src/modules/modem/analog/ModemIQ.h
Normal file
27
Software/CubicSDR/src/modules/modem/analog/ModemIQ.h
Normal 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:
|
||||
|
||||
};
|
||||
64
Software/CubicSDR/src/modules/modem/analog/ModemLSB.cpp
Normal file
64
Software/CubicSDR/src/modules/modem/analog/ModemLSB.cpp
Normal 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);
|
||||
}
|
||||
25
Software/CubicSDR/src/modules/modem/analog/ModemLSB.h
Normal file
25
Software/CubicSDR/src/modules/modem/analog/ModemLSB.h
Normal 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;
|
||||
};
|
||||
39
Software/CubicSDR/src/modules/modem/analog/ModemNBFM.cpp
Normal file
39
Software/CubicSDR/src/modules/modem/analog/ModemNBFM.cpp
Normal 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);
|
||||
}
|
||||
23
Software/CubicSDR/src/modules/modem/analog/ModemNBFM.h
Normal file
23
Software/CubicSDR/src/modules/modem/analog/ModemNBFM.h
Normal 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;
|
||||
};
|
||||
65
Software/CubicSDR/src/modules/modem/analog/ModemUSB.cpp
Normal file
65
Software/CubicSDR/src/modules/modem/analog/ModemUSB.cpp
Normal 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
Reference in New Issue
Block a user