diff --git a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/AudioOutput.rdb b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/AudioOutput.rdb index 0534a41..07ecee2 100644 Binary files a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/AudioOutput.rdb and b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/AudioOutput.rdb differ diff --git a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/GlobalFileTable.000 b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/GlobalFileTable.000 index 45c3edf..0799989 100644 Binary files a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/GlobalFileTable.000 and b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/GlobalFileTable.000 differ diff --git a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/GlobalFileTable.001 b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/GlobalFileTable.001 index 12ed4dd..88c0aff 100644 Binary files a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/GlobalFileTable.001 and b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/GlobalFileTable.001 differ diff --git a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerry.npa00 b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerry.npa00 new file mode 100644 index 0000000..c43a910 Binary files /dev/null and b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerry.npa00 differ diff --git a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerry.npd b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerry.npd new file mode 100644 index 0000000..3728a83 Binary files /dev/null and b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerry.npd differ diff --git a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerry.rdb b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerry.rdb new file mode 100644 index 0000000..ce35ec5 Binary files /dev/null and b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerry.rdb differ diff --git a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerrySettings.npa01 b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerrySettings.npa01 index 5525dba..5e302ef 100644 Binary files a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerrySettings.npa01 and b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerrySettings.npa01 differ diff --git a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerrySettings.npd b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerrySettings.npd index 9d720ed..212ba1e 100644 Binary files a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerrySettings.npd and b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerrySettings.npd differ diff --git a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerrySettings.rdb b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerrySettings.rdb index a2b2de9..dfd3334 100644 Binary files a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerrySettings.rdb and b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerrySettings.rdb differ diff --git a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerryStreaming.hpa00 b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerryStreaming.hpa00 new file mode 100644 index 0000000..4412d5a Binary files /dev/null and b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerryStreaming.hpa00 differ diff --git a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerryStreaming.hpd b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerryStreaming.hpd new file mode 100644 index 0000000..c6166df Binary files /dev/null and b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerryStreaming.hpd differ diff --git a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerryStreaming.npa00 b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerryStreaming.npa00 index 9747337..3d37a67 100644 Binary files a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerryStreaming.npa00 and b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerryStreaming.npa00 differ diff --git a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerryStreaming.npd b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerryStreaming.npd index 2e62d8f..54d5e3a 100644 Binary files a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerryStreaming.npd and b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerryStreaming.npd differ diff --git a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerryStreaming.rdb b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerryStreaming.rdb index 8f4564a..82ff76e 100644 Binary files a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerryStreaming.rdb and b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/SoapyHifiBerryStreaming.rdb differ diff --git a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/codedb.lck b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/codedb.lck index 88366bd..12df6d0 100644 --- a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/codedb.lck +++ b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/codedb.lck @@ -1 +1 @@ -This file was last opened by PID 33640 +This file was last opened by PID 19252 diff --git a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/tmp00000003.npa00 b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/tmp00000003.npa00 new file mode 100644 index 0000000..446622e Binary files /dev/null and b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/tmp00000003.npa00 differ diff --git a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/tmp00000003.npa01 b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/tmp00000003.npa01 index 35c29c6..650f722 100644 Binary files a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/tmp00000003.npa01 and b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/tmp00000003.npa01 differ diff --git a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/tmp00000003.npd b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/tmp00000003.npd index 8068453..61545a4 100644 Binary files a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/tmp00000003.npd and b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/tmp00000003.npd differ diff --git a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/tmp00000003.rdb b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/tmp00000003.rdb index 7ba5849..b66fcbc 100644 Binary files a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/tmp00000003.rdb and b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/tmp00000003.rdb differ diff --git a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/tmp00000006.npa00 b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/tmp00000006.npa00 index 65c195a..5281106 100644 Binary files a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/tmp00000006.npa00 and b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/tmp00000006.npa00 differ diff --git a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/tmp00000006.npd b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/tmp00000006.npd index 690d99c..82052a7 100644 Binary files a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/tmp00000006.npd and b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/tmp00000006.npd differ diff --git a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/tmp00000006.rdb b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/tmp00000006.rdb index c71f191..7973c23 100644 Binary files a/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/tmp00000006.rdb and b/.visualgdb/CodeDB/SoapyHifiBerry-Release-VisualGDB/tmp00000006.rdb differ diff --git a/.visualgdb/CodeExplorer/NodeFlags.dat b/.visualgdb/CodeExplorer/NodeFlags.dat index 630783f..1fc8f50 100644 Binary files a/.visualgdb/CodeExplorer/NodeFlags.dat and b/.visualgdb/CodeExplorer/NodeFlags.dat differ diff --git a/.visualgdb/VisualGDBCache/SoapyHifiBerry-Release/SysprogsSyncCache/00.dat b/.visualgdb/VisualGDBCache/SoapyHifiBerry-Release/SysprogsSyncCache/00.dat index f0d5655..8537881 100644 Binary files a/.visualgdb/VisualGDBCache/SoapyHifiBerry-Release/SysprogsSyncCache/00.dat and b/.visualgdb/VisualGDBCache/SoapyHifiBerry-Release/SysprogsSyncCache/00.dat differ diff --git a/SoapyHifiBerry.h b/SoapyHifiBerry.h index e50b9a9..c8050a0 100644 --- a/SoapyHifiBerry.h +++ b/SoapyHifiBerry.h @@ -23,7 +23,7 @@ #include "AudioInput.h" #include "AudioOutput.h" #include "si5351.h" - +#include "configfile.h" typedef enum hifiberrysdrStreamFormat { @@ -156,8 +156,12 @@ class SoapyHifiBerry : public SoapySDR::Device unique_ptr uptr_audiooutput; unique_ptr uptr_audioinput; + unique_ptr uptr_cfg; DataBuffer source_buffer_rx; DataBuffer source_buffer_tx; unique_ptr pSI5351; + + int get_int(string section, string key); + string get_string(string section, string key); }; diff --git a/SoapyHifiBerry.vcxproj b/SoapyHifiBerry.vcxproj index 4d33377..c65085b 100644 --- a/SoapyHifiBerry.vcxproj +++ b/SoapyHifiBerry.vcxproj @@ -77,20 +77,26 @@ + + + + + + diff --git a/SoapyHifiBerry.vcxproj.filters b/SoapyHifiBerry.vcxproj.filters index a9fd0d7..83c270c 100644 --- a/SoapyHifiBerry.vcxproj.filters +++ b/SoapyHifiBerry.vcxproj.filters @@ -40,6 +40,15 @@ Source files + + Source files + + + Source files + + + Source files + @@ -63,6 +72,15 @@ Header files + + Header files + + + Header files + + + Header files + diff --git a/SoapyHifiBerrySettings.cpp b/SoapyHifiBerrySettings.cpp index 5e24d35..2dd9485 100644 --- a/SoapyHifiBerrySettings.cpp +++ b/SoapyHifiBerrySettings.cpp @@ -12,6 +12,30 @@ /*********************************************************************** * Device interface **********************************************************************/ +const cfg::File::ConfigMap defaultOptions = { + {"si5351", {{"correction", cfg::makeOption("50000")}}}, + {"sound", {{"device", cfg::makeOption("snd_rpi_hifiberry_dacplusadcpro")}}} +}; + +int SoapyHifiBerry::get_int(string section, string key) +{ + auto option = uptr_cfg->getSection(section); + auto s = option.find(key); + if (s == option.end()) + return 0; + string st = s->second; + return atoi((const char *)st.c_str()); +} + +string SoapyHifiBerry::get_string(string section, string key) +{ + string st; + auto option = uptr_cfg->getSection(section); + auto s = option.find(key); + if (s != option.end()) + st = s->second; + return st; +} SoapyHifiBerry::SoapyHifiBerry(const SoapySDR::Kwargs &args) { @@ -20,6 +44,13 @@ SoapyHifiBerry::SoapyHifiBerry(const SoapySDR::Kwargs &args) SoapySDR_log(SOAPY_SDR_INFO, "SoapyHifiBerry::SoapyHifiBerry constructor called"); no_channels = 1; + uptr_cfg = make_unique(); + if (!uptr_cfg->loadFromFile("hifiberry.cfg")) + { + uptr_cfg->setDefaultOptions(defaultOptions); + uptr_cfg->writeToFile("hifiberry.cfg"); + } + uptr_audiooutput = make_unique(192000, &source_buffer_tx, RtAudio::LINUX_ALSA); uptr_audioinput = make_unique(192000, true, &source_buffer_rx, RtAudio::LINUX_ALSA); @@ -32,12 +63,15 @@ SoapyHifiBerry::SoapyHifiBerry(const SoapySDR::Kwargs &args) printf("Audio device %s\n", devices[i].c_str()); } } - uptr_audiooutput->open("snd_rpi_hifiberry_dacplusadcpro"); + int corr = get_int("si5351", "correction"); + string dev = get_string("sound", "device"); + + uptr_audiooutput->open(dev); uptr_audioinput->open(uptr_audiooutput->get_device()); pSI5351 = make_unique("/dev/i2c-1",SI5351_BUS_BASE_ADDR); pSI5351->init(SI5351_CRYSTAL_LOAD_8PF, 0, 0); - pSI5351->set_correction(-200000L, SI5351_PLL_INPUT_XO); + pSI5351->set_correction((long)corr, SI5351_PLL_INPUT_XO); pSI5351->drive_strength(CLK_VFO_RX, SI5351_DRIVE_2MA); pSI5351->drive_strength(CLK_VFO_TX, SI5351_DRIVE_2MA); pSI5351->output_enable(CLK_VFO_RX, 1); @@ -69,7 +103,7 @@ std::string SoapyHifiBerry::getHardwareKey(void) const } SoapySDR::Kwargs SoapyHifiBerry::getHardwareInfo(void) const -{ +{ SoapySDR_log(SOAPY_SDR_INFO, "SoapyHifiBerry::getHardwareInfo called"); diff --git a/SoapyHifiBerryStreaming.cpp b/SoapyHifiBerryStreaming.cpp index d8e3c3e..4187d1b 100644 --- a/SoapyHifiBerryStreaming.cpp +++ b/SoapyHifiBerryStreaming.cpp @@ -7,6 +7,7 @@ #include #include #include +#include void SoapyHifiBerry::setSampleRate(const int direction, const size_t channel, const double rate) { @@ -127,6 +128,9 @@ void SoapyHifiBerry::closeStream(SoapySDR::Stream *stream) } } +std::queue delay_queue; +const int delay = 0; + int SoapyHifiBerry::readStream( SoapySDR::Stream *handle, void *const *buffs, @@ -147,13 +151,26 @@ int SoapyHifiBerry::readStream( uptr_audioinput->read(iqsamples); for (auto con : iqsamples) { - target_buffer[nr_samples++] = con; + IQSample iq = con; + if (delay > 0) + { + delay_queue.push(iq.real()); + iq.real(0.0); + if (delay_queue.size() == delay) + { + iq.real(delay_queue.front()); + delay_queue.pop(); + } + } + target_buffer[nr_samples++] = iq; + //printf("I %f, Q %f\n", con.real(), con.imag()); } } - //printf("nr_samples %d sample: %d %d \n", nr_samples, left_sample, right_sample); + //printf("nr_samples %d sample: %d %d \n", nr_samples, left_sample, right_sample); return (nr_samples); //return the number of IQ samples } + int SoapyHifiBerry::writeStream(SoapySDR::Stream *handle, const void *const *buffs, const size_t numElems, int &flags, const long long timeNs, const long timeoutUs) { size_t ret; @@ -168,7 +185,6 @@ int SoapyHifiBerry::writeStream(SoapySDR::Stream *handle, const void *const *buf { for (int i = 0; i < numElems; i++) { - IQSample iq; iqsamples.push_back(target_buffer[i]); } uptr_audiooutput->write(iqsamples); diff --git a/configfile.cpp b/configfile.cpp new file mode 100644 index 0000000..7f824ce --- /dev/null +++ b/configfile.cpp @@ -0,0 +1,416 @@ +// Copyright (C) 2014-2016 Eric Hebert (ayebear) +// This code is licensed under MIT, see LICENSE.txt for details. + +#include "configfile.h" +#include +#include +#include "strlib.h" + +namespace cfg +{ + +File::File() +{ + setFlags(); +} + +File::File(const std::string& filename, int newFlags) +{ + setFlags(newFlags); + loadFromFile(filename); +} + +File::File(const ConfigMap& defaultOptions, int newFlags) +{ + setFlags(newFlags); + setDefaultOptions(defaultOptions); +} + +File::File(const std::string& filename, const ConfigMap& defaultOptions, int newFlags) +{ + setFlags(newFlags); + setDefaultOptions(defaultOptions); + loadFromFile(filename); +} + +File::~File() +{ + if (flags & Autosave) + writeToFile(); +} + +bool File::loadFromFile(const std::string& filename) +{ + configFilename = filename; + std::vector lines; + fileIoSuccessful = strlib::readLinesFromFile(configFilename, lines); + if (fileIoSuccessful) + parseLines(lines); + else if (flags & Verbose) + std::cout << "Error loading \"" << configFilename << "\"\n"; + return fileIoSuccessful; +} + +void File::loadFromString(const std::string& str) +{ + auto lines = strlib::getLinesFromString(str); + parseLines(lines); +} + +bool File::writeToFile(std::string filename) const +{ + if (filename.empty()) + filename = configFilename; + // Write the std::string to the output file + fileIoSuccessful = strlib::writeStringToFile(filename, buildString()); + if (!fileIoSuccessful && (flags & Verbose)) + std::cout << "Error writing \"" << configFilename << "\"\n"; + return fileIoSuccessful; +} + +void File::writeToString(std::string& str) const +{ + for (const auto& section: options) // Go through all of the sections + { + if (!section.first.empty()) + str += '[' + section.first + "]\n"; // Add the section line if it is not blank + for (const auto& o: section.second) // Go through all of the options in this section + str += o.first + " = " + o.second.buildArrayString() + '\n'; + str += '\n'; + } + if (!str.empty() && str.back() == '\n') + str.pop_back(); // Strip the extra new line at the end +} + +std::string File::buildString() const +{ + std::string configStr; + writeToString(configStr); + return configStr; +} + +File::operator bool() const +{ + return fileIoSuccessful; +} + +bool File::getStatus() const +{ + return fileIoSuccessful; +} + +void File::setFlag(int flag, bool state) +{ + if (state) + flags |= flag; + else + flags &= ~flag; +} + +void File::setFlags(int newFlags) +{ + flags = newFlags; +} + +Option& File::operator()(const std::string& name, const std::string& section) +{ + return options[section][name]; +} + +Option& File::operator()(const std::string& name) +{ + return options[currentSection][name]; +} + +bool File::optionExists(const std::string& name, const std::string& section) const +{ + auto sectionFound = options.find(section); + return (sectionFound != options.end() && sectionFound->second.find(name) != sectionFound->second.end()); +} + +bool File::optionExists(const std::string& name) const +{ + return optionExists(name, currentSection); +} + +void File::setDefaultOptions(const ConfigMap& defaultOptions) +{ + options.insert(defaultOptions.begin(), defaultOptions.end()); +} + +void File::useSection(const std::string& section) +{ + currentSection = section; +} + +File::ConfigMap::iterator File::begin() +{ + return options.begin(); +} + +File::ConfigMap::iterator File::end() +{ + return options.end(); +} + +File::Section& File::getSection(const std::string& section) +{ + return options[section]; +} + +File::Section& File::getSection() +{ + return options[currentSection]; +} + +bool File::sectionExists(const std::string& section) const +{ + return (options.find(section) != options.end()); +} + +bool File::sectionExists() const +{ + return sectionExists(currentSection); +} + +bool File::eraseOption(const std::string& name, const std::string& section) +{ + bool status = false; + auto sectionFound = options.find(section); + if (sectionFound != options.end()) // If the section exists + status = (sectionFound->second.erase(name) > 0); // Erase the option + return status; +} + +bool File::eraseOption(const std::string& name) +{ + return eraseOption(name, currentSection); +} + +bool File::eraseSection(const std::string& section) +{ + return (options.erase(section) > 0); +} + +bool File::eraseSection() +{ + return eraseSection(currentSection); +} + +void File::clear() +{ + options.clear(); +} + +void File::parseLines(std::vector& lines) +{ + currentArrayStack.clear(); + arrayOptionName.clear(); + std::string section; + bool multiLineComment = false; + Comment commentType = Comment::None; + for (std::string& line: lines) // Iterate through the std::vector of strings + { + strlib::trimWhitespace(line); + commentType = stripComments(line, multiLineComment); + + if (commentType == Comment::Start) + multiLineComment = true; + + if (!multiLineComment && !line.empty()) // Don't process a comment or an empty line + { + if (isSection(line)) + parseSectionLine(line, section); // Example: "[Section]" + else + parseOptionLine(line, section); // Example: "Option = Value" + } + + if (commentType == Comment::End) + multiLineComment = false; + } +} + +bool File::isSection(const std::string& section) const +{ + return (section.size() >= 2 && section.front() == '[' && section.back() == ']'); +} + +void File::parseSectionLine(const std::string& line, std::string& section) +{ + section = line.substr(1, line.size() - 2); // Set the current section + options[section]; // Add that section to the map +} + +void File::parseOptionLine(const std::string& line, const std::string& section) +{ + if (!currentArrayStack.empty()) + { + // Process another line in the array + // This could be 1 of 3 things: + // 1. { (array start) + // 2. X (another value) + // 3. } (array end) + std::string value = line; + strlib::trimWhitespace(value); + if (value.back() == ',') + value.pop_back(); + strlib::trimWhitespace(value); // In case there was whitespace before the comma + if (!value.empty()) + { + Option& option = getArrayOption(section, arrayOptionName); + if ((value == "{") || (value[0] == '{')) + { + startArray(option); + } + else if ((value == "}") || (value[0] == '}')) + currentArrayStack.pop_back(); + else + setOption(option.push(), value); + } + } + else + { + // Process a regular option line (or the start of a new array) + size_t equalPos = line.find("="); // Find the position of the "=" symbol + if (equalPos != std::string::npos && equalPos >= 1) // Ignore the line if there is no "=" symbol + { + std::string name, value; + // Extract the name and value + name = line.substr(0, equalPos); + value = line.substr(equalPos + 1); + // Trim any whitespace around the name and value + strlib::trimWhitespace(name); + strlib::trimWhitespace(value); + strlib::stripNewLines(value); + // Check if this is the start of an array + // Option& option = options[section][name]; +// ph002 change to support single line array +// like: myarray = {1,2,3,4} + if ((value == "{") || (value[0] == '{')) + { + bool bline = false; + arrayOptionName = name; + currentArrayStack.assign(1, 0); + if (value.find(',') > 0) + { + Option& option = getArrayOption(section, arrayOptionName); + bline = true; + while (value.find(',') != -1) + { + int n = value.find(','); + std::string s = value.substr(1, n-1); + strlib::trimWhitespace(s); + setOption(option.push(), s); + value.erase(1, n); + } + if (value.find('}') > 0) + { + int n = value.find('}'); + std::string s = value.substr(1, n - 1); + strlib::trimWhitespace(s); + setOption(option.push(), s); + } + } + if (bline) + { + currentArrayStack.clear(); + arrayOptionName.clear(); + } + } + else + { + Option &option = options[section][name]; + if (!setOption(option, value) && (flags & Verbose)) + { + std::cout << "Warning: Option \"" << name << "\" in [" << section << "] was out of range.\n"; + std::cout << " Using default value: " << option.toStringWithQuotes() << std::endl; + } + } + } + } +} + +bool File::setOption(Option& option, const std::string& value) +{ + std::string trimmedValue = value; + bool trimmedQuotes = trimQuotes(trimmedValue); // Remove quotes if any + bool optionSet = (option = trimmedValue); // Try to set the option + if (trimmedQuotes) // If quotes were removed + option.setQuotes(true); // Add quotes to the option + return optionSet; +} + +Option& File::getArrayOption(const std::string& section, const std::string& name) +{ + Option* currentOption = &options[section][name]; + // Start at 1, because the 0th element represents the current option above + for (unsigned i = 1; i < currentArrayStack.size(); ++i) + currentOption = &((*currentOption)[currentArrayStack[i]]); + return *currentOption; +} + +void File::startArray(Option& option) +{ + option.push(); // This new option will be holding the array starting with this "{" + currentArrayStack.push_back(option.size() - 1); +} + +bool File::areQuotes(char c1, char c2) +{ + // Both characters must be the same, either single quotes or double quotes + return ((c1 == c2) && (c1 == '"' || c1 == '\'')); +} + +bool File::trimQuotes(std::string& str) +{ + bool status = false; + if (str.size() >= 2 && areQuotes(str.front(), str.back())) + { + // Remove the quotes + str.pop_back(); + str.erase(str.begin()); + status = true; + } + return status; +} + +bool File::isEndComment(const std::string& str) const +{ + return (str.find("*/") != std::string::npos); +} + +File::Comment File::getCommentType(const std::string& str, bool checkEnd) const +{ + // Comment symbols and types + static const std::vector commentSymbols = {"/*", "//", "#", "::", ";"}; + static const std::vector commentTypes = {Comment::Start, Comment::Single, Comment::Single, Comment::Single, Comment::Single}; + + Comment commentType = Comment::None; + + // This is optional so the function returns the correct result + if (checkEnd && isEndComment(str)) + commentType = Comment::End; + + // Find out which comment type the string is, if any + for (unsigned int i = 0; (commentType == Comment::None && i < commentSymbols.size()); i++) + { + if (str.compare(0, commentSymbols[i].size(), commentSymbols[i]) == 0) + commentType = commentTypes[i]; + } + + // This check is required for comments using the multi-line symbols on a single line + if (!checkEnd && commentType == Comment::Start && isEndComment(str)) + commentType = Comment::Single; + + return commentType; +} + +File::Comment File::stripComments(std::string& str, bool checkEnd) +{ + Comment commentType = getCommentType(str, checkEnd); + if (commentType == Comment::Single) + str.clear(); + return commentType; +} + +} diff --git a/configfile.h b/configfile.h new file mode 100644 index 0000000..accff63 --- /dev/null +++ b/configfile.h @@ -0,0 +1,123 @@ +// Copyright (C) 2014-2016 Eric Hebert (ayebear) +// This code is licensed under MIT, see LICENSE.txt for details. + +#ifndef CFG_FILE_H +#define CFG_FILE_H + +#include +#include +#include +#include "configoption.h" + +namespace cfg +{ + +/* +A class for reading/writing configuration files. +See README.md for more information. + +TODO: + Handle escape codes inside of strings. + Handle multiple array elements on the same line. + Support sub-sections by using a tree, similar to Option arrays. + Writing should optionally preserve formatting and comments. +*/ +class File +{ + public: + enum Flags + { + NoFlags = 0b00, + Verbose = 0b01, // Display file IO errors and when options are out of range + Autosave = 0b10, // Automatically save the last file loaded on destruction + AllFlags = 0b11 + }; + static const int DefaultFlags = Verbose; + + // Types used to store the options + using Section = std::map; + using ConfigMap = std::map; + + // Constructors + File(); + File(const std::string& filename, int newFlags = DefaultFlags); + File(const ConfigMap& defaultOptions, int newFlags = DefaultFlags); + File(const std::string& filename, const ConfigMap& defaultOptions, int newFlags = DefaultFlags); + ~File(); + + // Loading/saving + bool loadFromFile(const std::string& filename); // Loads options from a file + void loadFromString(const std::string& str); // Loads options from a string + bool writeToFile(std::string filename = "") const; // Saves current options to a file (default is last loaded) + void writeToString(std::string& str) const; // Saves current options to a string (same format as writeToFile) + std::string buildString() const; // Returns a string of the current options (same format as writeToFile) + explicit operator bool() const; // Returns true if the last file loaded/saved successfully + bool getStatus() const; // Returns true if the last file loaded/saved successfully + + // Settings + void setFlag(int flag, bool state = true); // Turns a flag on/off + void setFlags(int newFlags = DefaultFlags); // Overwrites all flags + + // Accessing/modifying options + Option& operator()(const std::string& name, const std::string& section); // Returns a reference to an option with the specified name (and section). If it does not exist, it will be automatically created + Option& operator()(const std::string& name); // Same as above but uses the current section + bool optionExists(const std::string& name, const std::string& section) const; // Returns true if an option exists + bool optionExists(const std::string& name) const; // Returns true if an option exists + void setDefaultOptions(const ConfigMap& defaultOptions); // Sets initial values in the map from another map in memory + ConfigMap::iterator begin(); // Returns an iterator to the beginning of the map + ConfigMap::iterator end(); // Returns an iterator to the end of the map + + // Accessing/modifying sections + void useSection(const std::string& section = ""); // Sets the default current section to be used + Section& getSection(const std::string& section); // Returns a reference to a section + Section& getSection(); // Returns a reference to the default section + bool sectionExists(const std::string& section) const; // Returns true if a section exists + bool sectionExists() const; // Returns true if a section exists + + // Erasing options/sections + bool eraseOption(const std::string& name, const std::string& section); // Erases an option, returns true if the option was successfully erased + bool eraseOption(const std::string& name); // Erases an option from the default section + bool eraseSection(const std::string& section); // Erases a section, returns true if the section was successfully erased + bool eraseSection(); // Erases the default section + void clear(); // Clears all of the sections and options in memory, but keeps the filename + + private: + enum class Comment + { + None, // No comment + Single, // Single line comment + Start, // Start of multiple line comment + End // End of multiple line comment + }; + + // File parsing + void parseLines(std::vector& lines); // Processes the lines in memory and adds them to the options map + bool isSection(const std::string& section) const; // Returns true if the line is a section header + void parseSectionLine(const std::string& line, std::string& section); // Processes a section header line and adds a section to the map + void parseOptionLine(const std::string& line, const std::string& section); // Processes an option line and adds an option to the map + bool setOption(Option& option, const std::string& value); // Sets an existing option + Option& getArrayOption(const std::string& section, const std::string& name); // Returns an option at the current array level + void startArray(Option& option); // Starts another array when "{" is found + bool areQuotes(char c1, char c2); // Returns true if both characters are either single or double quotes + bool trimQuotes(std::string& str); // Trims quotes on ends of string, returns true if the string was modified + + // Comment handling + bool isEndComment(const std::string& str) const; // Returns true if it contains a multiple-line end comment symbol + Comment getCommentType(const std::string& str, bool checkEnd = false) const; // Returns an enum value of the comment type + Comment stripComments(std::string& str, bool checkEnd = false); // Removes all comments from a string + + // Objects/variables + ConfigMap options; // The data structure for storing all of the options in memory + std::string configFilename; // The filename of the config file to read/write to + std::string currentSection; // The default current section + int flags; // Flag bits are stored in here + mutable bool fileIoSuccessful; + + // Array related objects + std::vector currentArrayStack; // Stack of array indices for current option + std::string arrayOptionName; // Name of option whose array is currently being handled +}; + +} + +#endif diff --git a/configoption.cpp b/configoption.cpp new file mode 100644 index 0000000..6bc7e2e --- /dev/null +++ b/configoption.cpp @@ -0,0 +1,286 @@ +// Copyright (C) 2014-2016 Eric Hebert (ayebear) +// This code is licensed under MIT, see LICENSE.txt for details. + +#include "configoption.h" + +namespace cfg +{ + +Option::OptionVector Option::emptyVector; + +Option::Option(const std::string& data) +{ + setString(data); +} + +Option::Option(const Option& data) +{ + operator=(data); +} + +void Option::reset() +{ + removeRange(); + options.reset(); + operator=(0); +} + +bool Option::operator=(const char* data) +{ + return setString(data); +} + +bool Option::operator=(const std::string& data) +{ + return setString(data); +} + +Option& Option::operator=(const Option& data) +{ + text = data.text; + integer = data.integer; + decimal = data.decimal; + boolean = data.boolean; + quotes = data.quotes; + minEnabled = data.minEnabled; + maxEnabled = data.maxEnabled; + rangeMin = data.rangeMin; + rangeMax = data.rangeMax; + if (data.options) + options = std::make_unique(*data.options); + else + options.reset(); + return *this; +} + +bool Option::setString(const std::string& data) +{ + std::istringstream stream(data); + double value{}; + + // Try to parse a value from the string + bool success = (stream >> value) && (static_cast(stream.gcount()) == data.size()); + + // Only set the value if it's in range + if (isInRange(value)) + { + decimal = value; + integer = decimal; + text = data; + + // Check for a boolean value + auto lowerStr = strlib::toLower(data); + bool isTrue = (lowerStr == "true"); + bool isFalse = (lowerStr == "false"); + + // Determine if quotes are necessary + quotes = !(success || isFalse || isTrue); + + // Convert to a boolean ("true" means true, or any non-zero value) + boolean = (success ? (decimal != 0) : isTrue); + + return true; + } + return false; +} + +const std::string& Option::toString() const +{ + return text; +} + +std::string Option::toStringWithQuotes() const +{ + // Automatically append quotes to the string if it originally had them + return (quotes ? ('"' + text + '"') : text); +} + +int Option::toInt() const +{ + return integer; +} + +long Option::toLong() const +{ + return static_cast(integer); +} + +float Option::toFloat() const +{ + return static_cast(decimal); +} + +double Option::toDouble() const +{ + return decimal; +} + +bool Option::toBool() const +{ + return boolean; +} + +char Option::toChar() const +{ + return static_cast(integer); +} + +void Option::get(std::string& val) const +{ + val = text; +} + +void Option::get(long& val) const +{ + val = integer; +} + +void Option::get(double& val) const +{ + val = decimal; +} + +void Option::get(bool& val) const +{ + val = boolean; +} + +Option::operator const std::string&() const +{ + return text; +} + +void Option::setQuotes(bool setting) +{ + quotes = setting; +} + +bool Option::hasQuotes() +{ + return quotes; +} + +void Option::setMin(double minimum) +{ + rangeMin = minimum; + minEnabled = true; +} + +void Option::setMax(double maximum) +{ + rangeMax = maximum; + maxEnabled = true; +} + +void Option::setRange(double minimum, double maximum) +{ + setMin(minimum); + setMax(maximum); +} + +void Option::removeRange() +{ + minEnabled = false; + maxEnabled = false; +} + +Option& Option::push(const Option& opt) +{ + if (!options) + options = std::make_unique(); + options->push_back(opt); + return options->back(); +} + +void Option::pop() +{ + if (options && !options->empty()) + options->pop_back(); +} + +Option& Option::operator[](unsigned pos) +{ + return ((*options)[pos]); +} + +Option& Option::back() +{ + return options->back(); +} + +unsigned Option::size() const +{ + return (options ? options->size() : 0); +} + +void Option::clear() +{ + options.reset(); +} + +Option::OptionVector::iterator Option::begin() +{ + if (options) + return options->begin(); + return emptyVector.begin(); +} + +Option::OptionVector::iterator Option::end() +{ + if (options) + return options->end(); + return emptyVector.end(); +} + +Option::OptionVector::const_iterator Option::cbegin() const +{ + if (options) + return options->cbegin(); + return emptyVector.cbegin(); +} + +Option::OptionVector::const_iterator Option::cend() const +{ + if (options) + return options->cend(); + return emptyVector.cend(); +} + +std::string Option::buildArrayString(const std::string& indentStr) const +{ + // Continue building array strings until the option is just a single element and not an array + if (options) + { + std::string nextIndentStr(indentStr); // + //std::string nextIndentStr(indentStr + '\t'); // + + // Build the array string + std::string arrayStr("{"); //std::string arrayStr("{\n"); + unsigned arraySize = options->size(); + for (unsigned i = 0; i < arraySize; ++i) + { + arrayStr += nextIndentStr; + arrayStr += (*options)[i].buildArrayString(nextIndentStr); + if (i < arraySize - 1) + arrayStr += ", "; //arrayStr += ",\n"; + } + arrayStr += indentStr + '}'; //arrayStr += '\n' + indentStr + '}'; + return arrayStr; + } + else + return toStringWithQuotes(); +} + +bool Option::isInRange(double num) +{ + return ((!minEnabled || num >= rangeMin) && + (!maxEnabled || num <= rangeMax)); +} + +std::ostream& operator<<(std::ostream& stream, const Option& option) +{ + stream << option.toString(); + return stream; +} + +} diff --git a/configoption.h b/configoption.h new file mode 100644 index 0000000..c8b518b --- /dev/null +++ b/configoption.h @@ -0,0 +1,200 @@ +// Copyright (C) 2014-2016 Eric Hebert (ayebear) +// This code is licensed under MIT, see LICENSE.txt for details. + +#ifndef CFG_OPTION_H +#define CFG_OPTION_H + +#include +#include +#include +#include +#include "strlib.h" + +namespace cfg +{ + +// This class can store a value of different types based on a string +class Option +{ + using OptionVector = std::vector