diff --git a/CaptureLoop.cpp b/CaptureLoop.cpp index 9298f41..e5a455b 100644 --- a/CaptureLoop.cpp +++ b/CaptureLoop.cpp @@ -99,7 +99,7 @@ void CaptureLoop::run() { void CaptureLoop::_captureLoop() { //Used to temporarily store sample(for all out channels) while they are being processed. - double renderBlockBuffer[16]; + double renderBlockBuffer[8]; const size_t nChannelsIn = _pInputs->size(); const size_t nChannelsOut = _pOutputs->size(); //The size of all sample frames for all channels with the same sample index/timestamp @@ -147,7 +147,7 @@ void CaptureLoop::_captureLoop() { #ifdef PERFORMANCE_LOG if (flags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY) { - LOG_WARN("%s: AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY: %d\n", Date::getLocalDateTimeString().c_str(), samplesAvailable); + LOG_WARN("%s: AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY: %d", Date::getLocalDateTimeString().c_str(), samplesAvailable); } #endif @@ -234,7 +234,7 @@ void CaptureLoop::_checkClippingChannels() { for (Output * const pOutput : *_pOutputs) { const double clipping = pOutput->resetClipping(); if (clipping != 0.0) { - LOG_WARN("WARNING: Output(%s) - Clipping detected: +%0.2f dBFS\n", pOutput->getName().c_str(), Convert::levelToDb(clipping)); + LOG_WARN("WARNING: Output(%s) - Clipping detected: +%0.2f dBFS", Channels::toString(pOutput->getChannel()).c_str(), Convert::levelToDb(clipping)); } } } @@ -255,7 +255,8 @@ void CaptureLoop::_updateConditionalRouting() { //For debug purposes only. void CaptureLoop::_printUsedChannels() { for (size_t i = 0; i < _pInputs->size(); ++i) { - LOG_INFO("%s %d", (*_pInputs)[i]->getName().c_str(), _pUsedChannels[i]); + const Channel channel = (*_pInputs)[i]->getChannel(); + LOG_INFO("%s %d", Channels::toString(channel).c_str(), _pUsedChannels[i]); } LOG_NL(); } \ No newline at end of file diff --git a/Channel.cpp b/Channel.cpp new file mode 100644 index 0000000..b3a682f --- /dev/null +++ b/Channel.cpp @@ -0,0 +1,59 @@ +#include "Channel.h" +#include "Error.h" +#include "Str.h" + +const Channel Channels::fromString(const std::string &strIn) { + const std::string str = String::toUpperCase(strIn); + if (str.compare("L") == 0) { + return Channel::L; + } + else if (str.compare("R") == 0) { + return Channel::R; + } + else if (str.compare("C") == 0) { + return Channel::C; + } + else if (str.compare("SW") == 0) { + return Channel::SW; + } + else if (str.compare("SL") == 0) { + return Channel::SL; + } + else if (str.compare("SR") == 0) { + return Channel::SR; + } + else if (str.compare("SBL") == 0) { + return Channel::SBL; + } + else if (str.compare("SBR") == 0) { + return Channel::SBR; + } + throw Error("Unknown channel '%s'", strIn.c_str()); +} + +const std::string Channels::toString(const Channel channel) { + switch (channel) { + case Channel::L: + return "L"; + case Channel::R: + return "R"; + case Channel::C: + return "C"; + case Channel::SW: + return "SW"; + case Channel::SL: + return "SL"; + case Channel::SR: + return "SR"; + case Channel::SBL: + return "SBL"; + case Channel::SBR: + return "SBR"; + default: + throw Error("Unknown channel type %d", channel); + }; +} + +const std::string Channels::toString(const size_t channelIndex) { + return toString((Channel)channelIndex); +} \ No newline at end of file diff --git a/Channel.h b/Channel.h new file mode 100644 index 0000000..9e1b880 --- /dev/null +++ b/Channel.h @@ -0,0 +1,14 @@ +#pragma once +#include + +enum class Channel { + L, R, C, SW, SBL, SBR, SL, SR +}; + +namespace Channels { + + const Channel fromString(const std::string &str); + const std::string toString(const Channel channel); + const std::string toString(const size_t channelIndex); + +}; diff --git a/Config.cpp b/Config.cpp index 83a2764..3f81757 100644 --- a/Config.cpp +++ b/Config.cpp @@ -1,28 +1,22 @@ #include "Config.h" #define NOMINMAX #include //cin -#include //std::min -#include -#include "Error.h" -#include "FilterType.h" -#include "SubType.h" -#include "JsonNode.h" #include "JsonParser.h" #include "Convert.h" #include "WinDSPLog.h" #include "AudioDevice.h" #include "Input.h" #include "Output.h" -#include "DSP.h" -#include "Str.h" #include "Visibility.h" -#include "CrossoverTypes.h" +#include "FilterGain.h" +#include "Channel.h" Config::Config(const std::string &path) { _configFile = path; _hide = _minimize = _useConditionalRouting = _startWithOS = false; - _sampleRate = _numChannelsIn = _numChannelsOut = _numChannelsRender = 0; + _sampleRate = _numChannelsIn = _numChannelsOut = 0; _lastModified = 0; + _pLpFilter = _pHpFilter = nullptr; load(); parseMisc(); parseDevices(); @@ -35,13 +29,11 @@ Config::~Config() { for (Output *p : _outputs) { delete p; } - for (JsonNode *p : _tmpJsonNodes) { - //tmp list contains shallow copies. Need to remove children first othervise they will be deleted twice and cause errors. - p->clear(); - delete p; - } delete _pJsonNode; _pJsonNode = nullptr; + delete _pLpFilter; + delete _pHpFilter; + _pLpFilter = _pHpFilter = nullptr; } void Config::init(const uint32_t sampleRate, const uint32_t numChannelsIn, const uint32_t numChannelsOut) { @@ -49,8 +41,11 @@ void Config::init(const uint32_t sampleRate, const uint32_t numChannelsIn, const _numChannelsIn = numChannelsIn; _numChannelsOut = numChannelsOut; _useConditionalRouting = false; - parseInputs(); + parseRouting(); parseOutputs(); + + //TODO + printConfig(); } const std::string Config::getCaptureDeviceName() const { @@ -81,157 +76,6 @@ const bool Config::minimize() const { return _minimize; } -void Config::parseMisc() { - //Parse visibility options - _hide = _pJsonNode->has("hide") ? boolValue(_pJsonNode, "hide", "") : false; - _minimize = _pJsonNode->has("minimize") ? boolValue(_pJsonNode, "minimize", "") : false; - - //Parse startup - _startWithOS = _pJsonNode->has("startWithOS") ? boolValue(_pJsonNode, "startWithOS", "") : false; - - //Parse channels - const std::string path = "channels"; - const JsonNode *pChannels = _pJsonNode->path(path); - if (!pChannels->isMissingNode()) { - if (!pChannels->isArray()) { - throw Error("Config(%s) - Field is not an array/list", path.c_str()); - } - for (size_t i = 0; i < pChannels->size(); ++i) { - const std::string channelName = textValue(pChannels->get(i), String::format("%s/%d", path.c_str(), i)); - _channelNames.push_back(channelName); - } - } - else { - //Default channel order. - _channelNames = { "L", "R", "C", "SW", "SBL", "SBR", "SL", "SR" }; - } -} - -void Config::parseDevices() { - const JsonNode *pDvicesNode = _pJsonNode->path("devices"); - const JsonNode *pCaptureNode = pDvicesNode->path("capture"); - const JsonNode *pRenderNode = pDvicesNode->path("render"); - - //Devices not set in config. Query user - if (!pCaptureNode->has("name") || !pRenderNode->has("name")) { - setDevices(); - parseDevices(); - } - //Devices already set in config. - else { - _captureDeviceName = textValue(pCaptureNode, "name", "devices/capture"); - _renderDeviceName = textValue(pRenderNode, "name", "devices/render"); - _numChannelsRender = 0; - } -} - -void Config::parseInputs() { - //Create default in to out routing - _inputs = std::vector(_numChannelsIn); - //Iterate inputs and set routes - const std::string path = "inputs"; - const JsonNode *pInputs = _pJsonNode->path(path); - for (std::string channelName : pInputs->getOrder()) { - parseInput(pInputs, channelName, path); - } - //Add default in/out route to missing - for (size_t i = 0; i < _numChannelsIn; ++i) { - if (!_inputs[i]) { - //Output exists. Route to output - if (i < _numChannelsOut) { - _inputs[i] = new Input(getChannelName(i), i); - } - //Output doesn't exists. Add default non-route input. - else { - _inputs[i] = new Input(getChannelName(i)); - } - } - } -} - -void Config::parseInput(const JsonNode *pInputs, const std::string &channelName, std::string path) { - const size_t channelIn = getChannelIndex(channelName, path); - if (channelIn >= (int)_numChannelsIn) { - LOG_WARN("WARNING: Config(%s) - Capture device doesn't have channel '%s'\n", path.c_str(), channelName.c_str()); - return; - } - const JsonNode *pChannelNode = getNode(pInputs, channelName, path); - _inputs[channelIn] = new Input(channelName); - const JsonNode *pRoutes = pChannelNode->path("routes"); - path = path + "/" + "routes"; - for (size_t i = 0; i < pRoutes->size(); ++i) { - parseRoute(_inputs[channelIn], pRoutes, i, path); - } -} - -void Config::parseRoute(Input *pInput, const JsonNode *pRoutes, const size_t index, std::string path) { - const JsonNode *pRouteNode = getNode(pRoutes, index, path); - //If route have no out channel it's the same thing as no route at all. - if (pRouteNode->has("out")) { - const std::string outPath = path + "/out"; - const std::string channelName = textValue(pRouteNode->get("out"), outPath); - const size_t channelOut = getChannelIndex(channelName, outPath); - if (channelOut >= (int)_numChannelsOut) { - LOG_WARN("WARNING: Config(%s) - Render device doesn't have channel '%s'\n", outPath.c_str(), channelName.c_str()); - return; - } - Route *pRoute = new Route(channelOut); - pRoute->addFilters(parseFilters(pRouteNode, path)); - parseConditions(pRoute, pRouteNode, path); - pInput->addRoute(pRoute); - } -} - -void Config::parseConditions(Route *pRoute, const JsonNode *pRouteNode, std::string path) { - if (pRouteNode->has("if")) { - const JsonNode *pIfNode = getNode(pRouteNode, "if", path); - if (pIfNode->has("silent")) { - const std::string channelName = textValue(pIfNode, "silent", path); - const size_t channel = getChannelIndex(channelName, path + "/silent"); - pRoute->addCondition(Condition(ConditionType::SILENT, (int)channel)); - } - else { - LOG_WARN("WARNING: Config(%s) - Unknown if condition", path.c_str()); - return; - } - _useConditionalRouting = true; - } -} - -void Config::parseOutputs() { - _outputs = std::vector(_numChannelsOut); - //Iterate outputs and add filters - const std::string path = "outputs"; - const JsonNode *pOutputs = _pJsonNode->path(path); - for (const std::string &channelName : pOutputs->getOrder()) { - parseOutput(pOutputs, channelName, path); - } - //Add default empty output to missing - for (size_t i = 0; i < _outputs.size(); ++i) { - if (!_outputs[i]) { - _outputs[i] = new Output(getChannelName(i)); - } - } - validateLevels(path); -} - -void Config::parseOutput(const JsonNode *pOutputs, const std::string &channelName, std::string path) { - const size_t channel = getChannelIndex(channelName, path); - if (channel >= (int)_numChannelsOut) { - LOG_WARN("WARNING: Config(%s) - Render device doesn't have channel '%s'\n", path.c_str(), channelName.c_str()); - return; - } - const JsonNode *pChannelNode = getNode(pOutputs, channelName, path); - bool mute = false; - if (pChannelNode->has("mute")) { - mute = boolValue(pChannelNode, "mute", path); - } - Output *pOutput = new Output(channelName, mute); - pOutput->addFilters(parseFilters(pChannelNode, path)); - pOutput->addPostFilters(parsePostFilters(pChannelNode, path)); - _outputs[channel] = pOutput; -} - void Config::validateLevels(const std::string &path) const { std::vector levels; //Init levels to silence/0 @@ -257,10 +101,10 @@ void Config::validateLevels(const std::string &path) const { //Level is above 0dBFS. CLIPPING CAN OCCURE!!! if (levels[i] > 1.0) { if (first) { - LOG_WARN("WARNING: Config(%s) - Sum of routed channel levels is above 0dBFS on output channel. CLIPPING CAN OCCUR!\n", path.c_str()); + LOG_WARN("WARNING: Config(%s) - Sum of routed channel levels is above 0dBFS on output channel. CLIPPING CAN OCCUR!", path.c_str()); first = false; } - LOG_WARN("\t%s: +%.2f dBFS\n", getChannelName(i, path).c_str(), Convert::levelToDb(levels[i])); + LOG_WARN("\t%s: +%.2f dBFS", Channels::toString(i).c_str(), Convert::levelToDb(levels[i])); } } if (!first) { @@ -279,330 +123,6 @@ const double Config::getFilterGainSum(const std::vector &filters, doubl return startLevel; } -const std::vector Config::parseFilters(const JsonNode *pNode, std::string path) { - std::vector filters; - //Parse single instance simple filters - parseGain(filters, pNode, path); - parseDelay(filters, pNode, path); - //Parse filters list - FilterBiquad *pFilterBiquad = new FilterBiquad(_sampleRate); - const JsonNode *pFiltersNode = pNode->path("filters"); - for (size_t i = 0; i < pFiltersNode->size(); ++i) { - std::string tmpPath = path + "/filters"; - const JsonNode *pFilter = getNode(pFiltersNode, i, tmpPath); - parseFilter(filters, pFilterBiquad, pFilter, tmpPath); - } - //No biquads added. Don't use biquad filter. - if (pFilterBiquad->isEmpty()) { - delete pFilterBiquad; - } - //Use biquad filter - else { - filters.push_back(pFilterBiquad); - } - return filters; -} - -const std::vector Config::parsePostFilters(const JsonNode *pNode, std::string path) { - std::vector filters; - parseCancellation(filters, pNode, path); - return filters; -} - -void Config::parseCancellation(std::vector &filters, const JsonNode *pNode, std::string path) const { - if (pNode->has("cancellation")) { - const JsonNode *pFilterNode = pNode->path("cancellation"); - const double freq = doubleValue(pFilterNode, "freq", path); - if (pFilterNode->has("gain")) { - const double gain = doubleValue(pFilterNode, "gain", path); - filters.push_back(new FilterCancellation(_sampleRate, freq, gain)); - } - else { - filters.push_back(new FilterCancellation(_sampleRate, freq)); - } - } -} - -void Config::parseGain(std::vector &filters, const JsonNode *pNode, std::string path) { - double value = 0.0; - bool invert = false; - if (pNode->has("gain")) { - value = doubleValue(pNode, "gain", path); - } - if (pNode->has("invert")) { - invert = boolValue(pNode, "invert", path); - } - //No use in adding zero gain. - if (value != 0.0 || invert) { - filters.push_back(new FilterGain(value, invert)); - } -} - -void Config::parseDelay(std::vector &filters, const JsonNode *pNode, std::string path) { - pNode = getNode(pNode, "delay", path); - if (pNode->isMissingNode()) { - return; - } - double value; - double useUnitMeter = false; - if (pNode->isNumber()) { - value = doubleValue(pNode, path); - } - else if (pNode->isObject()) { - value = doubleValue(pNode, "value", path); - if (pNode->has("unitMeter")) { - useUnitMeter = boolValue(pNode->path("unitMeter"), path + "/unitMeter"); - } - } - else { - throw Error("Config(%s) - Invalid delay format", path.c_str()); - } - //No use in adding zero delay. - if (value != 0) { - const int sampleDelay = FilterDelay::getSampleDelay(_sampleRate, value, useUnitMeter); - if (sampleDelay > 0) { - filters.push_back(new FilterDelay(sampleDelay)); - } - else { - LOG_WARN("WARNING: Config(%s) - Discarding delay filter with to low value. Can't delay less then one sample\n", path.c_str()); - } - } -} - -void Config::parseFilter(std::vector &filters, FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string path) const { - //Is array. Iterate and parse each filter. - if (pFilterNode->isArray()) { - for (size_t i = 0; i < pFilterNode->size(); ++i) { - parseFilter(filters, pFilterBiquad, pFilterNode->get(i), path + "/" + std::to_string(i)); - } - return; - } - const std::string typeStr = textValue(pFilterNode, "type", path); - const FilterType type = FilterTypes::fromString(typeStr); - switch (type) { - case FilterType::LOW_PASS: - case FilterType::HIGH_PASS: - parseCrossover(type == FilterType::LOW_PASS, pFilterBiquad, pFilterNode, path); - break; - case FilterType::LOW_SHELF: - case FilterType::HIGH_SHELF: - parseShelf(type == LOW_SHELF, pFilterBiquad, pFilterNode, path); - break; - case FilterType::PEQ: - parsePEQ(pFilterBiquad, pFilterNode, path); - break; - case FilterType::BAND_PASS: - parseBandPass(pFilterBiquad, pFilterNode, path); - break; - case FilterType::NOTCH: - parseNotch(pFilterBiquad, pFilterNode, path); - break; - case FilterType::LINKWITZ_TRANSFORM: - parseLinkwitzTransform(pFilterBiquad, pFilterNode, path); - break; - case FilterType::BIQUAD: - parseBiquad(pFilterBiquad, pFilterNode, path); - break; - case FilterType::FIR: - parseFir(filters, pFilterNode, path); - break; - default: - throw Error("Config(%s) - Unknown filter type '%s'", path.c_str(), FilterTypes::toString(type)); - }; -} - -void Config::parseCrossover(const bool isLowPass, FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string path) const { - const std::string subTypeStr = textValue(pFilterNode, "subType", path); - const SubType subType = SubTypes::fromString(subTypeStr); - const double freq = doubleValue(pFilterNode, "freq", path); - const int order = intValue(pFilterNode, "order", path); - switch (subType) { - case SubType::BUTTERWORTH: - pFilterBiquad->addCrossover(isLowPass, freq, order, CrossoverType::Butterworth); - break; - case SubType::LINKWITZ_RILEY: - pFilterBiquad->addCrossover(isLowPass, freq, order, CrossoverType::Linkwitz_Riley); - break; - case SubType::BESSEL: - pFilterBiquad->addCrossover(isLowPass, freq, order, CrossoverType::Bessel); - break; - case SubType::CUSTOM: { - const JsonNode *pQNode = getField(pFilterNode, "q", path); - std::vector qValues; - int calculatedOrder = 0; - for (size_t i = 0; i < pQNode->size(); ++i) { - const double q = doubleValue(pQNode->get(i), String::format("%s/%d", path.c_str(), i)); - calculatedOrder += q < 0 ? 1 : 2; - qValues.push_back(q); - } - if (calculatedOrder != order) { - throw Error("Config(%s) - CROSSOVER.CUSTOM: Q values list doesn't match order. Expected(%d), Found(%d)", path.c_str(), order, calculatedOrder); - } - pFilterBiquad->addCrossover(isLowPass, freq, qValues); - break; - } - default: - throw Error("Config(%s) - Unknown crossover sub type %d", path.c_str(), SubTypes::toString(subType)); - } -} - -void Config::parseShelf(const bool isLowShelf, FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string path) const { - const double freq = doubleValue(pFilterNode, "freq", path); - const double gain = doubleValue(pFilterNode, "gain", path); - if (pFilterNode->has("q")) { - const double q = doubleValue(pFilterNode, "q", path); - pFilterBiquad->addShelf(isLowShelf, freq, gain, q); - } - else { - pFilterBiquad->addShelf(isLowShelf, freq, gain); - } -} - -void Config::parsePEQ(FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string path) const { - const double freq = doubleValue(pFilterNode, "freq", path); - const double q = doubleValue(pFilterNode, "q", path); - const double gain = doubleValue(pFilterNode, "gain", path); - pFilterBiquad->addPEQ(freq, q, gain); -} - -void Config::parseBandPass(FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string path) const { - const double freq = doubleValue(pFilterNode, "freq", path); - const double bandwidth = doubleValue(pFilterNode, "bandwidth", path); - if (pFilterNode->has("gain")) { - const double gain = doubleValue(pFilterNode, "gain", path); - pFilterBiquad->addBandPass(freq, bandwidth, gain); - } - else { - pFilterBiquad->addBandPass(freq, bandwidth); - } -} - -void Config::parseNotch(FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string path) const { - const double freq = doubleValue(pFilterNode, "freq", path); - const double bandwidth = doubleValue(pFilterNode, "bandwidth", path); - if (pFilterNode->has("gain")) { - const double gain = doubleValue(pFilterNode, "gain", path); - pFilterBiquad->addNotch(freq, bandwidth, gain); - } - else { - pFilterBiquad->addNotch(freq, bandwidth); - } -} - -void Config::parseLinkwitzTransform(FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string path) const { - const double f0 = doubleValue(pFilterNode, "f0", path); - const double q0 = doubleValue(pFilterNode, "q0", path); - const double fp = doubleValue(pFilterNode, "fp", path); - const double qp = doubleValue(pFilterNode, "qp", path); - pFilterBiquad->addLinkwitzTransform(f0, q0, fp, qp); -} - -void Config::parseBiquad(FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string path) const { - const JsonNode *pValues = getField(pFilterNode, "values", path); - for (size_t i = 0; i < pValues->size(); ++i) { - const JsonNode *pValueNode = pValues->get(i); - const double b0 = doubleValue(pValueNode->path("b0"), path); - const double b1 = doubleValue(pValueNode->path("b1"), path); - const double b2 = doubleValue(pValueNode->path("b2"), path); - const double a1 = doubleValue(pValueNode->path("a1"), path); - const double a2 = doubleValue(pValueNode->path("a2"), path); - if (pValueNode->has("a0")) { - const double a0 = doubleValue(pValueNode->path("a0"), path); - pFilterBiquad->add(b0, b1, b2, a0, a1, a2); - } - else { - pFilterBiquad->add(b0, b1, b2, a1, a2); - } - } -} - -void Config::parseFir(std::vector &filters, const JsonNode *pFilterNode, std::string path) const { - //Read each line in fir parameter file - const File file(textValue(pFilterNode, "file", path)); - path = path + "/file"; - const std::string extension = file.getExtension(); - if (extension.compare("txt") == 0) { - parseFirTxt(filters, file, path); - } - else if (extension.compare("wav") == 0) { - parseFirWav(filters, file, path); - } - else { - throw Error("Config(%s) - Unknown file extension for FIR file '%s'", path.c_str(), file.getPath().c_str()); - } -} - -void Config::parseFirTxt(std::vector &filters, const File &file, std::string path) const { - std::vector lines; - if (!file.getData(lines)) { - throw Error("Config(%s) - Can't read FIR file '%s'", path.c_str(), file.getPath().c_str()); - } - if (!lines.size()) { - throw Error("Config(%s) - FIR file '%s' is empty", path.c_str(), file.getPath().c_str()); - } - std::vector taps; - for (const std::string &str : lines) { - taps.push_back(atof(str.c_str())); - } - filters.push_back(new FilterFir(taps)); -} - -void Config::parseFirWav(std::vector &filters, const File &file, std::string path) const { - char *pBuffer = nullptr; - try { - //Get data - const size_t bufferSize = file.getData(&pBuffer); - WaveHeader header; - memcpy(&header, pBuffer, sizeof(header)); - const char *pData = pBuffer + sizeof(header); - if (header.numChannels != 1) { - throw Error("Config(%s) - FIR file is not mono", path.c_str()); - } - if (header.sampleRate != _sampleRate) { - throw Error("Config(%s) - FIR file sample rate doesn't match render device. Render(%d), FIR(%d)", path.c_str(), _sampleRate, header.sampleRate); - } - //Generate taps - const size_t numSamples = header.getNumSamples(); - std::vector taps; - if (header.audioFormat == WAVE_FORMAT_PCM) { - switch (header.bitsPerSample) { - case 16: - taps = Convert::pcm16ToDouble(pData, numSamples); - break; - case 24: - taps = Convert::pcm24ToDouble(pData, numSamples); - break; - case 32: - taps = Convert::pcm32ToDouble(pData, numSamples); - break; - default: - throw Error("Config(%s) - FIR file bit depth is unsupported: %u", path.c_str(), header.bitsPerSample); - } - } - else if (header.audioFormat == WAVE_FORMAT_IEEE_FLOAT) { - switch (header.bitsPerSample) { - case 32: - taps = Convert::float32ToDouble(pData, numSamples); - break; - case 64: - taps = Convert::float64ToDouble(pData, numSamples); - break; - default: - throw Error("Config(%s) - FIR file bit depth is unsupported: %u", path.c_str(), header.bitsPerSample); - } - } - else { - throw Error("Config(%s) - FIR file is in unknown audio format: %u", path.c_str(), header.audioFormat); - } - delete[] pBuffer; - filters.push_back(new FilterFir(taps)); - } - catch (const Error &e) { - delete[] pBuffer; - throw e; - } -} - void Config::setDevices() { Visibility::show(true); const std::vector wasapiDevices = AudioDevice::getDeviceNames(); @@ -661,122 +181,6 @@ void Config::setDevices() { save(); } -const JsonNode* Config::getField(const JsonNode *pNode, const std::string &field, const std::string &path) const { - const JsonNode *pResult = pNode->path(field); - if (pResult->isMissingNode()) { - throw Error("Config(%s) - Field '%s' is required", path.c_str(), field.c_str()); - } - return pResult; -} - -const JsonNode* Config::getNode(const JsonNode *pNode, const std::string &field, std::string &path) { - const JsonNode *pResult = pNode->path(field); - path = path + "/" + field; - const JsonNode *pRefNode = pResult->path("#ref"); - if (!pRefNode->isMissingNode()) { - pRefNode = getReference(pRefNode, path); - pResult = getEnrichedReference(pRefNode, pResult); - } - return pResult; -} - -const JsonNode* Config::getNode(const JsonNode *pNode, const size_t index, std::string &path) { - const JsonNode *pResult = pNode->path(index); - path = path + "/" + std::to_string(index); - const JsonNode *pRefNode = pResult->path("#ref"); - if (!pRefNode->isMissingNode()) { - pResult = getReference(pRefNode, path); - } - return pResult; -} - -const JsonNode* Config::getEnrichedReference(const JsonNode *pRefNode, const JsonNode *pOrgNode) { - //The "#ref" field is the only one present. No need to create enriched copy. - if (pOrgNode->size() == 1) { - return pRefNode; - } - JsonNode *pCopy = new JsonNode(JsonNodeType::OBJECT); - //Store in tmp list so destructor can take care of them. - _tmpJsonNodes.push_back(pCopy); - //First copy ref fields. - for (const auto &pair : pRefNode->getFields()) { - pCopy->put(pair.first, pair.second); - } - //Then copy org fields. IE org fields have presedence in a conflict. - for (const auto &pair : pOrgNode->getFields()) { - if (pair.first.compare("#ref") != 0) { - pCopy->put(pair.first, pair.second); - } - } - return pCopy; -} - -const JsonNode* Config::getReference(const JsonNode *pRefNode, std::string &path) const { - path = path + "/#ref"; - const std::string refPath = textValue(pRefNode, path); - JsonNode *pNode = _pJsonNode; - char buf[500]; - strcpy(buf, refPath.c_str()); - char *part = strtok(buf, "/"); - while (part != NULL) { - pNode = pNode->path(part); - part = strtok(NULL, "/"); - } - if (pNode->isMissingNode()) { - throw Error("Config(%s) - Can't find reference '%s'", path.c_str(), refPath.c_str()); - } - path = path + " -> " + refPath; - return pNode; -} - -const double Config::doubleValue(const JsonNode *pNode, const std::string &field, const std::string &path) const { - const JsonNode *pResult = getField(pNode, field, path); - return doubleValue(pResult, path + "/" + field); -} - -const double Config::doubleValue(const JsonNode *pNode, const std::string &path) const { - if (!pNode->isNumber()) { - throw Error("Config(%s) - Field is not a number", path.c_str()); - } - return pNode->doubleValue(); -} - -const int Config::intValue(const JsonNode *pNode, const std::string &field, const std::string &path) const { - const JsonNode *pResult = getField(pNode, field, path); - return intValue(pResult, path + "/" + field); -} - -const int Config::intValue(const JsonNode *pNode, const std::string &path) const { - if (!pNode->isInteger()) { - throw Error("Config(%s) - Field is not an integer number", path.c_str()); - } - return pNode->intValue(); -} - -const std::string Config::textValue(const JsonNode *pNode, const std::string &field, const std::string &path) const { - const JsonNode *pResult = getField(pNode, field, path); - return textValue(pResult, path + "/" + field); -} - -const std::string Config::textValue(const JsonNode *pNode, const std::string &path) const { - if (!pNode->isText()) { - throw Error("Config(%s) - Field is not a text string", path.c_str()); - } - return pNode->textValue(); -} - -const bool Config::boolValue(const JsonNode *pNode, const std::string &field, const std::string &path) const { - const JsonNode *pResult = getField(pNode, field, path); - return boolValue(pResult, path + "/" + field); -} - -const bool Config::boolValue(const JsonNode *pNode, const std::string &path) const { - if (!pNode->isBoolean()) { - throw Error("Config(%s) - Field is not a boolean", path.c_str()); - } - return pNode->boolValue(); -} - const size_t Config::getSelection(const size_t start, const size_t end, const size_t blacklist) const { char buf[10]; int value; @@ -787,36 +191,6 @@ const size_t Config::getSelection(const size_t start, const size_t end, const si return value; } -const size_t Config::getChannelIndex(const std::string &channelName, const std::string &path) const { - for (size_t i = 0; i < _channelNames.size(); ++i) { - if (_channelNames[i].compare(channelName) == 0) { - return i; - } - } - throw Error("Config(%s) - Unknown channel '%s'", path.c_str(), channelName.c_str()); -} - -const std::string Config::getChannelName(const size_t channelIndex, const std::string &path) const { - if (channelIndex >= _channelNames.size()) { - throw Error("Config(%s) - Unknown channel index: %d", path.c_str(), channelIndex); - } - return _channelNames[channelIndex]; -} - -const uint32_t Config::getNumChannelsRender(const uint32_t capacity) const { - if (_numChannelsRender > 0) { - return _numChannelsRender; - } - return (uint32_t)std::min((uint32_t)_channelNames.size(), capacity); -} - -const std::string Config::getChannelName(const size_t channelIndex) const { - if (channelIndex >= _channelNames.size()) { - throw Error("Unknown channel index: %d", channelIndex); - } - return _channelNames[channelIndex]; -} - void Config::load() { _pJsonNode = JsonParser::fromFile(_configFile); _lastModified = _configFile.getLastModifiedTime(); @@ -828,9 +202,44 @@ void Config::save() { } const std::string Config::getDescription() const { - return textValue(_pJsonNode, "description", ""); + return getTextValue(_pJsonNode, "description", ""); } const bool Config::hasDescription() const { return _pJsonNode->has("description"); +} + +void Config::printConfig() const { + printf("***** Inputs *****\n"); + for (const Input *pI : _inputs) { + printf("%s\t", Channels::toString(pI->getChannel()).c_str()); + for (const Route *pR : pI->getRoutes()) { + const double level = getFilterGainSum(pR->getFilters()); + const double gain = Convert::levelToDb(level); + printf("%s", Channels::toString(pR->getChannel()).c_str()); + if (gain) { + printf("(%.1f)", gain); + } + if (pR->hasConditions()) { + printf("(if)"); + } + printf(" "); + } + printf("\n"); + } + printf("\n"); + + printf("***** Outputs *****\n"); + for (const Output *pO : _outputs) { + printf("%s", Channels::toString(pO->getChannel()).c_str()); + const double level = getFilterGainSum(pO->getFilters()); + const double gain = Convert::levelToDb(level); + if (gain) { + printf("(%.1f)", gain); + } + printf(" "); + printf("\n"); + } + printf("\n"); + } \ No newline at end of file diff --git a/Config.h b/Config.h index cd910c0..46d93f4 100644 --- a/Config.h +++ b/Config.h @@ -1,13 +1,14 @@ /* - This class represents the config file - Used to load, parse and evaluate the JSON config + This class represents the config file + Used to load, parse and evaluate the JSON config - Author: Andreas Arvidsson - Source: https://github.com/AndreasArvidsson/WinDSP + Author: Andreas Arvidsson + Source: https://github.com/AndreasArvidsson/WinDSP */ #pragma once #include +#include #include "File.h" class Input; @@ -16,100 +17,174 @@ class Route; class Filter; class FilterBiquad; class JsonNode; +enum class SpeakerType; +enum class SubType; +enum class FilterType; +enum class Channel; class Config { public: - Config(const std::string &path); - ~Config(); + Config(const std::string &path); + ~Config(); - void init(const uint32_t sampleRate, const uint32_t numChannelsIn, const uint32_t numChannelsOut); + void init(const uint32_t sampleRate, const uint32_t numChannelsIn, const uint32_t numChannelsOut); - const std::string getCaptureDeviceName() const; - const std::string getRenderDeviceName() const; - const std::vector* getInputs() const; - const std::vector* getOutputs() const; - const bool hide() const; - const bool minimize() const; - const bool startWithOS() const; - const std::string getChannelName(const size_t channelIndex) const; - const uint32_t getNumChannelsRender(const uint32_t capacity) const; - const std::string getDescription() const; - const bool hasDescription() const; + const std::string getCaptureDeviceName() const; + const std::string getRenderDeviceName() const; + const std::vector* getInputs() const; + const std::vector* getOutputs() const; + const bool hide() const; + const bool minimize() const; + const bool startWithOS() const; + const std::string getDescription() const; + const bool hasDescription() const; - inline const bool Config::hasChanged() const { - return _lastModified != _configFile.getLastModifiedTime(); - } + inline const bool Config::hasChanged() const { + return _lastModified != _configFile.getLastModifiedTime(); + } - inline const bool useConditionalRouting() const { - return _useConditionalRouting; - } + inline const bool useConditionalRouting() const { + return _useConditionalRouting; + } private: - File _configFile; - JsonNode *_pJsonNode; - std::vector _tmpJsonNodes; - std::vector _inputs; - std::vector _outputs; - std::vector _channelNames; - std::string _captureDeviceName, _renderDeviceName; - uint32_t _sampleRate, _numChannelsIn, _numChannelsOut, _numChannelsRender; - time_t _lastModified; - bool _hide, _minimize, _useConditionalRouting, _startWithOS; - - void load(); - void save(); - - void parseDevices(); - void parseMisc(); - void parseInputs(); - void parseInput(const JsonNode *pInputs, const std::string &channelName, std::string path); - void parseRoute(Input *pInput, const JsonNode *pRoutes, const size_t index, std::string path); - void parseConditions(Route *pRoute, const JsonNode *pRouteNode, std::string path); - void parseOutputs(); - void parseOutput(const JsonNode *pOutputs, const std::string &channelName, std::string path); - - void validateLevels(const std::string &path) const; - const double getFilterGainSum(const std::vector &filters, double startLevel = 1.0) const; - - //void select(); - void setDevices(); - const size_t getSelection(const size_t start, const size_t end, const size_t blacklist = -1) const; - - const size_t getChannelIndex(const std::string &channelName, const std::string &path) const; - const std::string getChannelName(const size_t channelIndex, const std::string &path) const; - - const std::vector parseFilters(const JsonNode *pNode, const std::string path); - const std::vector parsePostFilters(const JsonNode *pNode, const std::string path); - void parseFilter(std::vector &filters, FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string path) const; - void parsePostFilter(std::vector &filters, FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string path) const; - void parseCrossover(const bool isLowPass, FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string path) const; - void parseShelf(const bool isLowShelf, FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string path) const; - void parsePEQ(FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string path) const; - void parseBandPass(FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string path) const; - void parseNotch(FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string path) const; - void parseLinkwitzTransform(FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string path) const; - void parseBiquad(FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string path) const; - void parseGain(std::vector &filters, const JsonNode *pNode, std::string path); - void parseDelay(std::vector &filters, const JsonNode *pNode, std::string path); - void parseCancellation(std::vector &filters, const JsonNode *pNode, std::string path) const; - void parseFir(std::vector &filters, const JsonNode *pFilterNode, std::string path) const; - void parseFirTxt(std::vector &filters, const File &file, std::string path) const; - void parseFirWav(std::vector &filters, const File &file, std::string path) const; - - const JsonNode* getNode(const JsonNode *pNode, const std::string &field, std::string &path); - const JsonNode* getNode(const JsonNode *pNode, const size_t index, std::string &path); - const JsonNode* getReference(const JsonNode *pRefNode, std::string &path) const; - const JsonNode* getField(const JsonNode *pNode, const std::string &field, const std::string &path) const; - const JsonNode* getEnrichedReference(const JsonNode *pRefNode, const JsonNode *pOrgNode); - - const double doubleValue(const JsonNode *pNode, const std::string &field, const std::string &path) const; - const double doubleValue(const JsonNode *pNode, const std::string &path) const; - const int intValue(const JsonNode *pNode, const std::string &field, const std::string &path) const; - const int intValue(const JsonNode *pNode, const std::string &path) const; - const std::string textValue(const JsonNode *pNode, const std::string &field, const std::string &path) const; - const std::string textValue(const JsonNode *pNode, const std::string &path) const; - const bool boolValue(const JsonNode *pNode, const std::string &field, const std::string &path) const; - const bool boolValue(const JsonNode *pNode, const std::string &path) const; + File _configFile; + JsonNode *_pJsonNode; + std::vector _inputs; + std::vector _outputs; + std::string _captureDeviceName, _renderDeviceName; + uint32_t _sampleRate, _numChannelsIn, _numChannelsOut; + time_t _lastModified; + bool _hide, _minimize, _useConditionalRouting, _startWithOS; + + + JsonNode *_pLpFilter, *_pHpFilter; + std::unordered_map _addLpTo, _addHpTo; + + + + /* ********* ConfigParserBasic.cpp ********* */ + + void parseBasic(); + const std::unordered_map parseChannels(const JsonNode *pBasicNode, const double swStereo, std::vector &subs, std::vector &subLs, std::vector &subRs, std::vector &smalls, const std::string &path); + void parseChannel(std::unordered_map &result, const JsonNode *pNode, const std::string &field, const std::vector &channels, const std::vector &allowed, const std::string &path) const; + const std::vector getChannelsByType(const std::unordered_map &channelsMap, const SpeakerType targetType) const; + const double getLfeGain(const JsonNode *pBasicNode, const bool useSubwoofers, const bool swStereo, const bool hasSmalls, const std::string &path) const; + void routeChannels(const std::unordered_map &channelsMap, const std::vector subs, const std::vector subLs, const std::vector subRs, const double lfeGain); + void addSwRoute(Input *pInput, const std::vector subs, const std::vector subLs, const std::vector subRs, const double lfeGain) const; + void addRoutes(Input *pInput, const std::vector channels, const double gain) const; + void addRoute(Input *pInput, const Channel channel, const double gain = 0) const; + const SpeakerType addRoute(const std::unordered_map &channelsMap, Input *pInput, const std::vector &channels) const; + void downmix(const std::unordered_map &channelsMap, Input *pInput, const std::vector subs, const std::vector subLs, const std::vector subRs, const double lfeGain) const; + const bool getUseSubwoofers(const std::vector &subs, const std::vector &subLs, const std::vector &subRs) const; + void parseExpandSurround(const JsonNode *pBasicNode, const std::unordered_map &channelsMap, const std::string &path); + void addIfRoute(Input *pInput, const Channel channel) const; + void parseCrossover(const JsonNode *pBasicNode, const std::unordered_map &channelsMap, const std::string &path); + + /* ********* ConfigParserAdvanced.cpp ********* */ + + void parseAdvanced(); + void parseInput(const JsonNode *pInputs, const std::string &channelName, std::string path); + void parseRoute(Input *pInput, const JsonNode *pRoutes, const size_t index, std::string path); + void parseConditions(Route *pRoute, const JsonNode *pRouteNode, std::string path); + + + /* ********* Config.cpp ********* */ + + void load(); + void save(); + void validateLevels(const std::string &path) const; + const double getFilterGainSum(const std::vector &filters, double startLevel = 1.0) const; + void setDevices(); + const size_t getSelection(const size_t start, const size_t end, const size_t blacklist = -1) const; + void printConfig() const; + + + /* ********* ConfigParser.cpp ********* */ + + void parseDevices(); + void parseMisc(); + void parseRouting(); + void parseOutputs(); + void parseOutput(const JsonNode *pOutputs, const size_t index, std::string path); + const std::vector getOutputChannels(const JsonNode *pOutputNode, const std::string &path); + void addBasicCrossovers(); + + /* ********* ConfigParserFilter.cpp ********* */ + + const std::vector parseFilters(const JsonNode *pNode, const std::string &path, const int outputChannel = -1); + const std::vector parsePostFilters(const JsonNode *pNode, const std::string &path); + void parseFilter(std::vector &filters, FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const int outputChannel, const std::string &path); + void parsePostFilter(std::vector &filters, FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string &path) const; + void parseCrossover(const bool isLowPass, FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string &path) const; + void parseShelf(const bool isLowShelf, FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string &path) const; + void parsePEQ(FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string &path) const; + void parseBandPass(FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string &path) const; + void parseNotch(FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string &path) const; + void parseLinkwitzTransform(FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string &path) const; + void parseBiquad(FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string &path) const; + void parseGain(std::vector &filters, const JsonNode *pNode, const std::string &path); + void parseDelay(std::vector &filters, const JsonNode *pNode, std::string path); + void parseCancellation(std::vector &filters, const JsonNode *pNode, const std::string &path) const; + void parseFir(std::vector &filters, const JsonNode *pFilterNode, const std::string &path) const; + void parseFirTxt(std::vector &filters, const File &file, const std::string &path) const; + void parseFirWav(std::vector &filters, const File &file, const std::string &path) const; + void updateCrossoverMaps(const bool isLP, const int outputChannel); + void applyCrossoversMap(FilterBiquad *pFilterBiquad, const int outputChannel); + + /* ********* ConfigParserUtil.cpp ********* */ + + const JsonNode* tryGetNode(const JsonNode *pNode, const std::string &field, std::string &path) const; + const JsonNode* tryGetNode(const JsonNode *pNode, const size_t index, std::string &path) const; + const JsonNode* getNode(const JsonNode *pNode, const std::string &field, std::string &path) const; + const JsonNode* getNode(const JsonNode *pNode, const size_t index, std::string &path) const; + + const JsonNode* tryGetObjectNode(const JsonNode *pNode, const std::string &field, std::string &path) const; + const JsonNode* tryGetObjectNode(const JsonNode *pNode, const size_t index, std::string &path) const; + const JsonNode* getObjectNode(const JsonNode *pNode, const std::string &field, std::string &path) const; + const JsonNode* getObjectNode(const JsonNode *pNode, const size_t index, std::string &path) const; + const JsonNode* validateObjectNode(const JsonNode *pNode, std::string &path, const bool optional = false) const; + + const JsonNode* tryGetArrayNode(const JsonNode *pNode, const std::string &field, std::string &path) const; + const JsonNode* tryGetArrayNode(const JsonNode *pNode, const size_t index, std::string &path) const; + const JsonNode* getArrayNode(const JsonNode *pNode, const std::string &field, std::string &path) const; + const JsonNode* getArrayNode(const JsonNode *pNode, const size_t index, std::string &path) const; + const JsonNode* validateArrayNode(const JsonNode *pNode, std::string &path, const bool optional = false) const; + + const JsonNode* _tryGetNodeInner(const JsonNode *pNode, std::string &path, const std::string &appendPath) const; + const JsonNode* _getNodeInner(const JsonNode *pNode, std::string &path) const; + const JsonNode* _getReference(const JsonNode *pParent, std::string &path) const; + + const std::string tryGetTextValue(const JsonNode *pNode, const std::string &field, const std::string &path) const; + const std::string tryGetTextValue(const JsonNode *pNode, const size_t index, const std::string &path) const; + const std::string getTextValue(const JsonNode *pNode, const std::string &field, const std::string &path) const; + const std::string getTextValue(const JsonNode *pNode, const size_t index, const std::string &path) const; + const std::string validateTextValue(const JsonNode *pNode, const std::string &path, const bool optional = false) const; + + const bool tryGetBoolValue(const JsonNode *pNode, const std::string &field, const std::string &path) const; + const bool tryGetBoolValue(const JsonNode *pNode, const size_t index, const std::string &path) const; + const bool getBoolValue(const JsonNode *pNode, const std::string &field, const std::string &path) const; + const bool getBoolValue(const JsonNode *pNode, const size_t index, const std::string &path) const; + const bool validateBoolValue(const JsonNode *pNode, const std::string &path, const bool optional = false) const; + + const double tryGetDoubleValue(const JsonNode *pNode, const std::string &field, const std::string &path) const; + const double tryGetDoubleValue(const JsonNode *pNode, const size_t index, const std::string &path) const; + const double getDoubleValue(const JsonNode *pNode, const std::string &field, const std::string &path) const; + const double getDoubleValue(const JsonNode *pNode, const size_t index, const std::string &path) const; + const double validateDoubleValue(const JsonNode *pNode, const std::string &path, const bool optional = false) const; + + const int tryGetIntValue(const JsonNode *pNode, const std::string &field, const std::string &path) const; + const int tryGetIntValue(const JsonNode *pNode, const size_t index, const std::string &path) const; + const int getIntValue(const JsonNode *pNode, const std::string &field, const std::string &path) const; + const int getIntValue(const JsonNode *pNode, const size_t index, const std::string &path) const; + const int validateIntValue(const JsonNode *pNode, const std::string &path, const bool optional = false) const; + + const SpeakerType getSpeakerType(const JsonNode *pNode, const std::string &field, const std::string &path) const; + const SpeakerType getSpeakerType(const JsonNode *pNode, const std::string &field, std::string &textOut, const std::string &path) const; + const FilterType getFilterType(const JsonNode *pNode, const std::string &field, const std::string &path) const; + const FilterType getFilterType(const JsonNode *pNode, const std::string &field, std::string &textOut, const std::string &path) const; + const SubType getSubType(const JsonNode *pNode, const std::string &field, const std::string &path) const; + const SubType getSubType(const JsonNode *pNode, const std::string &field, std::string &textOut, const std::string &path) const; }; \ No newline at end of file diff --git a/ConfigParser.cpp b/ConfigParser.cpp new file mode 100644 index 0000000..91da6d2 --- /dev/null +++ b/ConfigParser.cpp @@ -0,0 +1,109 @@ +#include "Config.h" +#include "WinDSPLog.h" +#include "JsonNode.h" +#include "Input.h" +#include "Output.h" +#include "Channel.h" + +void Config::parseDevices() { + const JsonNode *pDevicesNode = _pJsonNode->path("devices"); + //Devices not set in config. Query user + if (!pDevicesNode->has("capture") || !pDevicesNode->has("render")) { + setDevices(); + parseDevices(); + } + //Devices already set in config. + else { + _captureDeviceName = getTextValue(pDevicesNode, "capture", "devices"); + _renderDeviceName = getTextValue(pDevicesNode, "render", "devices"); + } +} + +void Config::parseMisc() { + //Parse visibility options + _hide = tryGetBoolValue(_pJsonNode, "hide", ""); + _minimize = tryGetBoolValue(_pJsonNode, "minimize", ""); + //Parse startup + _startWithOS = tryGetBoolValue(_pJsonNode, "startWithOS", ""); +} + +void Config::parseRouting() { + //Create list of in to out routings + _inputs = std::vector(_numChannelsIn); + + //Use basic or advanced routing + const bool hasBasic = _pJsonNode->has("basic"); + const bool hasAdvanced = _pJsonNode->has("advanced"); + if (hasBasic && hasAdvanced) { + throw Error("Config(/) - Found both basic and advanced routing. Use one!"); + } + if (hasBasic) { + parseBasic(); + } + else if (hasAdvanced) { + parseAdvanced(); + } + + //Add default in/out route to missing + for (size_t i = 0; i < _numChannelsIn; ++i) { + if (!_inputs[i]) { + //Output exists. Route to output + if (i < _numChannelsOut) { + _inputs[i] = new Input((Channel)i, (Channel)i); + } + //Output doesn't exists. Add default non-route input. + else { + _inputs[i] = new Input((Channel)i); + } + } + } +} + +void Config::parseOutputs() { + _outputs = std::vector(_numChannelsOut); + //Iterate outputs and add filters + std::string path = ""; + const JsonNode *pOutputs = tryGetArrayNode(_pJsonNode, "outputs", path); + for (size_t i = 0; i < pOutputs->size(); ++i) { + parseOutput(pOutputs, i, path); + } + //Add default empty output to missing + for (size_t i = 0; i < _outputs.size(); ++i) { + if (!_outputs[i]) { + _outputs[i] = new Output((Channel)i); + } + } + validateLevels(path); +} + +void Config::parseOutput(const JsonNode *pOutputs, const size_t index, std::string path) { + const JsonNode *pOutputNode = getObjectNode(pOutputs, index, path); + const std::vector channels = getOutputChannels(pOutputNode, path); + for (const Channel channel : channels) { + const bool mute = tryGetBoolValue(pOutputNode, "mute", path); + Output *pOutput = new Output(channel, mute); + pOutput->addFilters(parseFilters(pOutputNode, path, (int)channel)); + pOutput->addPostFilters(parsePostFilters(pOutputNode, path)); + _outputs[(size_t)channel] = pOutput; + } +} + +const std::vector Config::getOutputChannels(const JsonNode *pOutputNode, const std::string &path) { + std::vector result; + std::string channelsPath = path; + const JsonNode *pChannels = getArrayNode(pOutputNode, "channels", channelsPath); + for (size_t i = 0; i < pChannels->size(); ++i) { + const std::string channelName = getTextValue(pChannels, i, channelsPath); + const Channel channel = Channels::fromString(channelName); + const size_t channelIndex = (size_t)channel; + if (channelIndex >= _numChannelsOut) { + LOG_WARN("WARNING: Config(%s) - Render device doesn't have channel '%s'", channelsPath.c_str(), channelName.c_str()); + continue; + } + if (_outputs[channelIndex]) { + throw Error("Config(%s/%d) - Channel '%s' is already defiend/used", channelsPath.c_str(), i, Channels::toString(channel).c_str()); + } + result.push_back(channel); + } + return result; +} \ No newline at end of file diff --git a/ConfigParserAdvanced.cpp b/ConfigParserAdvanced.cpp new file mode 100644 index 0000000..37de7c7 --- /dev/null +++ b/ConfigParserAdvanced.cpp @@ -0,0 +1,72 @@ +#include "Config.h" +#include "JsonNode.h" +#include "Input.h" +#include "WinDSPLog.h" +#include "Channel.h" + +void Config::parseAdvanced() { + std::string path = ""; + //Iterate inputs and set routes + const JsonNode *pInputs = tryGetArrayNode(_pJsonNode, "advanced", path); + for (const std::string &channelName : pInputs->getOrder()) { + parseInput(pInputs, channelName, path); + } + //Add default in/out route to missing + for (size_t i = 0; i < _numChannelsIn; ++i) { + if (!_inputs[i]) { + //Output exists. Route to output + if (i < _numChannelsOut) { + _inputs[i] = new Input((Channel)i); + } + //Output doesn't exists. Add default non-route input. + else { + _inputs[i] = new Input((Channel)i); + } + } + } +} + +void Config::parseInput(const JsonNode *pInputs, const std::string &channelName, std::string path) { + const Channel channelIn = Channels::fromString(channelName); + const size_t channelIndex = (size_t)channelIn; + if (channelIndex >= _numChannelsIn) { + LOG_WARN("WARNING: Config(%s) - Capture device doesn't have channel '%s'", path.c_str(), channelName.c_str()); + return; + } + _inputs[channelIndex] = new Input(channelIn); + const JsonNode *pRoutes = tryGetArrayNode(pInputs, channelName, path); + for (size_t i = 0; i < pRoutes->size(); ++i) { + parseRoute(_inputs[channelIndex], pRoutes, i, path); + } +} + +void Config::parseRoute(Input *pInput, const JsonNode *pRoutes, const size_t index, std::string path) { + const JsonNode *pRouteNode = getObjectNode(pRoutes, index, path); + //If route have no out channel it's the same thing as no route at all. + if (pRouteNode->has("out")) { + const std::string outPath = path + "/out"; + const std::string channelName = getTextValue(pRouteNode, "out", path); + const Channel channelOut = Channels::fromString(channelName); + if ((size_t)channelOut >= (int)_numChannelsOut) { + LOG_WARN("WARNING: Config(%s) - Render device doesn't have channel '%s'", outPath.c_str(), channelName.c_str()); + return; + } + Route *pRoute = new Route(channelOut); + pRoute->addFilters(parseFilters(pRouteNode, path)); + parseConditions(pRoute, pRouteNode, path); + pInput->addRoute(pRoute); + } +} + +void Config::parseConditions(Route *pRoute, const JsonNode *pRouteNode, std::string path) { + if (pRouteNode->has("if")) { + const JsonNode *pIfNode = getObjectNode(pRouteNode, "if", path); + if (!pIfNode->has("silent")) { + throw Error("Config(%s) - Unknown if condition", path.c_str()); + } + const std::string channelName = getTextValue(pIfNode, "silent", path); + const size_t channel = (size_t)Channels::fromString(channelName); + pRoute->addCondition(Condition(ConditionType::SILENT, (int)channel)); + _useConditionalRouting = true; + } +} \ No newline at end of file diff --git a/ConfigParserBasic.cpp b/ConfigParserBasic.cpp new file mode 100644 index 0000000..87ac826 --- /dev/null +++ b/ConfigParserBasic.cpp @@ -0,0 +1,366 @@ +#include "Config.h" +#include "SpeakerType.h" +#include "JsonNode.h" +#include //std::find +#include "WinDSPLog.h" +#include "Channel.h" +#include "Input.h" +#include "FilterGain.h" + +void Config::parseBasic() { + std::string path = ""; + const JsonNode *pBasicNode = getObjectNode(_pJsonNode, "basic", path); + //Get channels + const double swStereo = tryGetBoolValue(pBasicNode, "stereoSubwoofer", path); + std::vector subs, subLs, subRs, smalls; + const std::unordered_map channelsMap = parseChannels(pBasicNode, swStereo, subs, subLs, subRs, smalls, path); + const bool useSubwoofers = getUseSubwoofers(subs, subLs, subRs); + //const bool swStereo = subLs.size(); + + + const double lfeGain = getLfeGain(pBasicNode, useSubwoofers, swStereo, smalls.size(), path); + + if (smalls.size() && !useSubwoofers) { + throw Error("Config(%s) - Can't use small speakers with no subwoofers", path.c_str()); + } + + //Route input to output channels + routeChannels(channelsMap, subs, subLs, subRs, lfeGain); + //Add conditional routing to surrounds + parseExpandSurround(pBasicNode, channelsMap, path); + + parseCrossover(pBasicNode, channelsMap, path); +} + +void Config::parseCrossover(const JsonNode *pBasicNode, const std::unordered_map &channelsMap, const std::string &path) { + for (const auto &e : channelsMap) { + switch (e.second) { + case SpeakerType::SMALL: + _addHpTo[e.first] = true; + break; + case SpeakerType::SUB: + _addLpTo[e.first] = true; + break; + } + } + + std::string lpPath = path; + const JsonNode *pLpNode = tryGetObjectNode(pBasicNode, "lp", lpPath); + _pLpFilter = new JsonNode(JsonNodeType::OBJECT); + for (const auto &e : pLpNode->getFields()) { + if (e.first.compare("type") == 0) { + _pLpFilter->put("subType", e.second); + } + else { + _pLpFilter->put(e.first, e.second); + } + } + if (!_pLpFilter->has("subType")) { + _pLpFilter->put("subType", "BUTTERWORTH"); + } + if (!_pLpFilter->has("freq")) { + _pLpFilter->put("freq", 80); + } + if (!_pLpFilter->has("order")) { + _pLpFilter->put("order", 5); + } + + std::string hpPath = path; + const JsonNode *pHpNode = tryGetObjectNode(pBasicNode, "hp", hpPath); + _pHpFilter = new JsonNode(JsonNodeType::OBJECT); + for (const auto &e : pHpNode->getFields()) { + if (e.first.compare("type") == 0) { + _pHpFilter->put("subType", e.second); + } + else { + _pHpFilter->put(e.first, e.second); + } + } + if (!_pHpFilter->has("subType")) { + _pHpFilter->put("subType", "BUTTERWORTH"); + } + if (!_pHpFilter->has("freq")) { + _pHpFilter->put("freq", 80); + } + if (!_pHpFilter->has("order")) { + _pHpFilter->put("order", 3); + } +} + +void Config::parseExpandSurround(const JsonNode *pBasicNode, const std::unordered_map &channelsMap, const std::string &path) { + const bool expandSurround = tryGetBoolValue(pBasicNode, "expandSurround", path); + if (expandSurround) { + const SpeakerType surrType = channelsMap.at(Channel::SL); + const SpeakerType surrBackType = channelsMap.at(Channel::SBL); + //All 4 surround channels must be playing as speakers + if (surrType != SpeakerType::LARGE && surrType != SpeakerType::SMALL || + surrBackType != SpeakerType::LARGE && surrBackType != SpeakerType::SMALL) { + LOG_WARN("WARNING: Config(%s/expandSurround) - Can't expand surround if not both surround and surround back are playing speakers as large/small", path.c_str()); + } + addIfRoute(_inputs[(size_t)Channel::SL], Channel::SBL); + addIfRoute(_inputs[(size_t)Channel::SR], Channel::SBR); + _useConditionalRouting = true; + } +} + +void Config::routeChannels(const std::unordered_map &channelsMap, const std::vector subs, const std::vector subLs, const std::vector subRs, const double lfeGain) { + for (size_t i = 0; i < _numChannelsIn; ++i) { + const Channel channel = (Channel)i; + Input *pInput = new Input(channel); + _inputs[i] = pInput; + const SpeakerType type = channelsMap.at(channel); + switch (type) { + //Route to matching speaker + case SpeakerType::LARGE: + addRoute(pInput, channel); + break; + //Route to matching speaker and sub + case SpeakerType::SMALL: + addRoute(pInput, channel); + addSwRoute(pInput, subs, subLs, subRs, lfeGain); + break; + //Downmix + case SpeakerType::OFF: + downmix(channelsMap, pInput, subs, subLs, subRs, lfeGain); + break; + //Sub or downmix + case SpeakerType::SUB: + if (channel == Channel::SW) { + addSwRoute(pInput, subs, subLs, subRs, lfeGain); + } + else { + downmix(channelsMap, pInput, subs, subLs, subRs, lfeGain); + } + break; + default: + LOG_WARN("Unmanaged type '%s'", SpeakerTypes::toString(type).c_str()); + } + } +} + +void Config::downmix(const std::unordered_map &channelsMap, Input *pInput, const std::vector subs, const std::vector subLs, const std::vector subRs, const double lfeGain) const { + SpeakerType type = SpeakerType::OFF; + switch (pInput->getChannel()) { + case Channel::SL: + type = addRoute(channelsMap, pInput, { Channel::SBL , Channel::L }); + break; + case Channel::SBL: + type = addRoute(channelsMap, pInput, { Channel::SL , Channel::L }); + break; + case Channel::SR: + type = addRoute(channelsMap, pInput, { Channel::SBR , Channel::R }); + break; + case Channel::SBR: + type = addRoute(channelsMap, pInput, { Channel::SR , Channel::R }); + break; + case Channel::C: + addRoute(pInput, Channel::L, -3); + addRoute(pInput, Channel::R, -3); + type = channelsMap.at(Channel::L); + break; + case Channel::SW: + if (getUseSubwoofers(subs, subLs, subRs)) { + type = SpeakerType::SMALL; + } + else { + addRoute(pInput, Channel::L, lfeGain); + addRoute(pInput, Channel::R, lfeGain); + } + break; + default: + LOG_WARN("Unmanaged downmix channel '%s'", Channels::toString(pInput->getChannel()).c_str()); + } + //Downmixed to a small speaker. Add to subs as well. + if (type == SpeakerType::SMALL) { + addSwRoute(pInput, subs, subLs, subRs, lfeGain); + } +} + +void Config::addSwRoute(Input *pInput, const std::vector subs, const std::vector subLs, const std::vector subRs, const double lfeGain) const { + const bool useSubwoofers = getUseSubwoofers(subs, subLs, subRs); + const bool swStereo = subLs.size(); + if (!useSubwoofers) { + return; + } + double gain = 0; + if (pInput->getChannel() == Channel::SW) { + gain = lfeGain; + } + //-6dB when routing center to stereo subs + else if (pInput->getChannel() == Channel::C && swStereo) { + gain = -6; + } + if (swStereo) { + switch (pInput->getChannel()) { + //Route to left sub channels + case Channel::L: + case Channel::SL: + case Channel::SBL: + addRoutes(pInput, subLs, gain); + break; + //Route to right sub channels + case Channel::R: + case Channel::SR: + case Channel::SBR: + addRoutes(pInput, subRs, gain); + break; + //Route to both sub channels + case Channel::C: + case Channel::SW: + addRoutes(pInput, subLs, gain); + addRoutes(pInput, subRs, gain); + } + } + else { + //Route to mono sub channels + addRoutes(pInput, subs, gain); + } +} + +const std::unordered_map Config::parseChannels(const JsonNode *pBasicNode, const double swStereo, std::vector &subs, std::vector &subLs, std::vector &subRs, std::vector &smalls, const std::string &path) { + std::unordered_map result; + //Front speakers are always enabled and normal sepakers + parseChannel(result, pBasicNode, "front", { Channel::L , Channel::R }, { SpeakerType::LARGE, SpeakerType::SMALL }, path); + //Subwoofer is always sub or off. + parseChannel(result, pBasicNode, "subwoofer", { Channel::SW }, { SpeakerType::SUB, SpeakerType::OFF }, path); + //Rest of channels can be either speaker, sub or off + const std::vector allowed = { SpeakerType::LARGE, SpeakerType::SMALL, SpeakerType::SUB, SpeakerType::OFF }; + parseChannel(result, pBasicNode, "center", { Channel::C }, allowed, path); + parseChannel(result, pBasicNode, "surround", { Channel::SL, Channel::SR }, allowed, path); + parseChannel(result, pBasicNode, "surroundBack", { Channel::SBL, Channel::SBR }, allowed, path); + + //Fill type lists + for (auto &e : result) { + switch (e.second) { + case SpeakerType::SMALL: + smalls.push_back(e.first); + break; + case SpeakerType::SUB: + if (swStereo) { + switch (e.first) { + case Channel::C: + case Channel::SL: + case Channel::SBL: + subLs.push_back(e.first); + break; + case Channel::SW: + case Channel::SR: + case Channel::SBR: + subRs.push_back(e.first); + break; + } + } + else { + subs.push_back(e.first); + } + break; + } + } + + if (subLs.size() != subRs.size()) { + throw Error("Config(%s) - Can't use stereo subwoofer if not both center(C) and subwoofer(SW) channels are used", path.c_str()); + } + + return result; +} + +void Config::parseChannel(std::unordered_map &result, const JsonNode *pNode, const std::string &field, const std::vector &channels, const std::vector &allowed, const std::string &path) const { + //Use give value + if (pNode->has(field)) { + std::string typeStr; + const SpeakerType type = getSpeakerType(pNode, field, typeStr, path); + if (std::find(allowed.begin(), allowed.end(), type) == allowed.end()) { + throw Error("Config(%s/%s) - Speaker type '%s' is not allowed", path.c_str(), field.c_str(), typeStr.c_str()); + } + for (const Channel channel : channels) { + //Speaker is set to playing but doesnt exist in render device + if ((size_t)channel >= _numChannelsOut && type != SpeakerType::OFF) { + LOG_WARN("WARNING: Config(%s/%s) - Render device doesn't have channel '%s'", path.c_str(), field.c_str(), Channels::toString(channel).c_str()); + continue; + } + result[channel] = type; + } + } + //Default value + else { + for (const Channel channel : channels) { + //Render device has this channel. Use default value. + if ((size_t)channel < _numChannelsOut) { + //First value is the default one. + result[channel] = allowed[0]; + } + //Render device doesnt have this channel. Set it to off + else { + result[channel] = SpeakerType::OFF; + } + } + } +} + +void Config::addRoutes(Input *pInput, const std::vector channels, const double gain) const { + for (const Channel channel : channels) { + addRoute(pInput, channel, gain); + } +} + +void Config::addRoute(Input *pInput, const Channel channel, const double gain) const { + if ((size_t)channel >= _numChannelsOut) { + LOG_WARN("WARNING: Config(Basic/channels) - Render device doesn't have channel '%s'", Channels::toString(channel).c_str()); + return; + } + Route *pRoute = new Route(channel); + if (gain) { + pRoute->addFilter(new FilterGain(gain)); + } + pInput->addRoute(pRoute); +} + +const SpeakerType Config::addRoute(const std::unordered_map &channelsMap, Input *pInput, const std::vector &channels) const { + for (const Channel channel : channels) { + if (channelsMap.find(channel) != channelsMap.end()) { + const SpeakerType type = channelsMap.at(channel); + if (type == SpeakerType::LARGE || type == SpeakerType::SMALL) { + addRoute(pInput, channel); + return type; + } + } + } + throw Error("No channel found for addRoute"); +} + +void Config::addIfRoute(Input *pInput, const Channel channel) const { + Route *pRoute = new Route(channel); + pRoute->addCondition(Condition(ConditionType::SILENT, (int)channel)); + pInput->addRoute(pRoute); +} + +const std::vector Config::getChannelsByType(const std::unordered_map &channelsMap, const SpeakerType targetType) const { + std::vector result; + for (const auto &e : channelsMap) { + if (e.second == targetType) { + result.push_back(e.first); + } + } + return result; +} + +const double Config::getLfeGain(const JsonNode *pBasicNode, const bool useSubwoofers, const bool swStereo, const bool hasSmalls, const std::string &path) const { + const double lfeGain = tryGetDoubleValue(pBasicNode, "lfeGain", path); + if (useSubwoofers) { + //No small speakers. IE LFE is not going to get mixed with other channels. + if (!hasSmalls) { + return 0; + } + //+10db for mono sub and +4dB for stereo sub to get correct level when mixed with other channels + return lfeGain + (swStereo ? 4 : 10); + } + //No sub. Downmix LFE to fronts + else { + return lfeGain + 4; + } + return 0; +} + +const bool Config::getUseSubwoofers(const std::vector &subs, const std::vector &subLs, const std::vector &subRs) const { + return subs.size() || subLs.size() || subRs.size(); +} \ No newline at end of file diff --git a/ConfigParserFilter.cpp b/ConfigParserFilter.cpp new file mode 100644 index 0000000..3ed8a88 --- /dev/null +++ b/ConfigParserFilter.cpp @@ -0,0 +1,370 @@ +#include "Config.h" +#include "DSP.h" +#include "JsonNode.h" +#include "WinDSPLog.h" +#include "FilterType.h" +#include "SubType.h" +#include "CrossoverTypes.h" +#include "Str.h" +#include "Convert.h" +#include "AudioDevice.h" //WAVE_FORMAT_PCM, WAVE_FORMAT_IEEE_FLOAT +#include "Channel.h" + +const std::vector Config::parseFilters(const JsonNode *pNode, const std::string &path, const int outputChannel) { + std::vector filters; + //Parse single instance simple filters. + parseGain(filters, pNode, path); + parseDelay(filters, pNode, path); + //Parse filters list + FilterBiquad *pFilterBiquad = new FilterBiquad(_sampleRate); + std::string filtersPath = path; + const JsonNode *pFiltersNode = tryGetArrayNode(pNode, "filters", filtersPath); + for (size_t i = 0; i < pFiltersNode->size(); ++i) { + std::string filterPath = filtersPath; + const JsonNode *pFilter = getObjectNode(pFiltersNode, i, filterPath); + parseFilter(filters, pFilterBiquad, pFilter, outputChannel, filterPath); + } + + //Apply crossovers from basic config. + applyCrossoversMap(pFilterBiquad, outputChannel); + + //No biquads added. Don't use biquad filter. + if (pFilterBiquad->isEmpty()) { + delete pFilterBiquad; + } + //Use biquad filter. + else { + filters.push_back(pFilterBiquad); + } + return filters; +} + +const std::vector Config::parsePostFilters(const JsonNode *pNode, const std::string &path) { + std::vector filters; + parseCancellation(filters, pNode, path); + return filters; +} + +void Config::parseCancellation(std::vector &filters, const JsonNode *pNode, const std::string &path) const { + if (pNode->has("cancellation")) { + const JsonNode *pFilterNode = pNode->path("cancellation"); + const double freq = getDoubleValue(pFilterNode, "freq", path); + if (pFilterNode->has("gain")) { + const double gain = getDoubleValue(pFilterNode, "gain", path); + filters.push_back(new FilterCancellation(_sampleRate, freq, gain)); + } + else { + filters.push_back(new FilterCancellation(_sampleRate, freq)); + } + } +} + +void Config::parseGain(std::vector &filters, const JsonNode *pNode, const std::string &path) { + const double value = tryGetDoubleValue(pNode, "gain", path); + const bool invert = tryGetBoolValue(pNode, "invert", path); + //No use in adding zero gain. + if (value != 0.0 || invert) { + filters.push_back(new FilterGain(value, invert)); + } +} + +void Config::parseDelay(std::vector &filters, const JsonNode *pNode, std::string path) { + pNode = tryGetNode(pNode, "delay", path); + if (pNode->isMissingNode()) { + return; + } + double value; + double useUnitMeter = false; + if (pNode->isNumber()) { + value = pNode->doubleValue(); + } + else if (pNode->isObject()) { + value = getDoubleValue(pNode, "value", path); + useUnitMeter = tryGetBoolValue(pNode, "unitMeter", path); + } + else { + throw Error("Config(%s) - Invalid delay format. Expected number or object", path.c_str()); + } + //No use in adding zero delay. + if (value != 0) { + const uint32_t sampleDelay = FilterDelay::getSampleDelay(_sampleRate, value, useUnitMeter); + if (sampleDelay > 0) { + filters.push_back(new FilterDelay(sampleDelay)); + } + else { + LOG_WARN("WARNING: Config(%s) - Discarding delay filter with to low value. Can't delay less then one sample", path.c_str()); + } + } +} + +void Config::parseFilter(std::vector &filters, FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const int outputChannel, const std::string &path) { + //Is array. Iterate and parse each filter. + if (pFilterNode->isArray()) { + for (size_t i = 0; i < pFilterNode->size(); ++i) { + parseFilter(filters, pFilterBiquad, pFilterNode->get(i), outputChannel, path + "/" + std::to_string(i)); + } + return; + } + std::string typeStr; + const FilterType type = getFilterType(pFilterNode, "type", typeStr, path); + switch (type) { + case FilterType::LOW_PASS: + case FilterType::HIGH_PASS: { + const bool isLP = type == FilterType::LOW_PASS; + parseCrossover(isLP, pFilterBiquad, pFilterNode, path); + //Update crossovers map from basic config. + updateCrossoverMaps(isLP, outputChannel); + break; + } + case FilterType::LOW_SHELF: + case FilterType::HIGH_SHELF: + parseShelf(type == FilterType::LOW_SHELF, pFilterBiquad, pFilterNode, path); + break; + case FilterType::PEQ: + parsePEQ(pFilterBiquad, pFilterNode, path); + break; + case FilterType::BAND_PASS: + parseBandPass(pFilterBiquad, pFilterNode, path); + break; + case FilterType::NOTCH: + parseNotch(pFilterBiquad, pFilterNode, path); + break; + case FilterType::LINKWITZ_TRANSFORM: + parseLinkwitzTransform(pFilterBiquad, pFilterNode, path); + break; + case FilterType::BIQUAD: + parseBiquad(pFilterBiquad, pFilterNode, path); + break; + case FilterType::FIR: + parseFir(filters, pFilterNode, path); + break; + default: + throw Error("Config(%s) - Unknown filter type '%s'", path.c_str(), typeStr.c_str()); + }; +} + +void Config::parseCrossover(const bool isLowPass, FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string &path) const { + std::string subTypeStr; + const SubType subType = getSubType(pFilterNode, "subType", subTypeStr, path); + const double freq = getDoubleValue(pFilterNode, "freq", path); + const int order = getIntValue(pFilterNode, "order", path); + switch (subType) { + case SubType::BUTTERWORTH: + pFilterBiquad->addCrossover(isLowPass, freq, order, CrossoverType::Butterworth); + break; + case SubType::LINKWITZ_RILEY: + pFilterBiquad->addCrossover(isLowPass, freq, order, CrossoverType::Linkwitz_Riley); + break; + case SubType::BESSEL: + pFilterBiquad->addCrossover(isLowPass, freq, order, CrossoverType::Bessel); + break; + case SubType::CUSTOM: { + std::string qPath = path; + const JsonNode *pQNode = getArrayNode(pFilterNode, "q", qPath); + std::vector qValues; + int calculatedOrder = 0; + for (size_t i = 0; i < pQNode->size(); ++i) { + const double q = getDoubleValue(pQNode, i, qPath); + calculatedOrder += q < 0 ? 1 : 2; + qValues.push_back(q); + } + if (calculatedOrder != order) { + throw Error("Config(%s) - CROSSOVER.CUSTOM: Q values list doesn't match order. Expected(%d), Found(%d)", path.c_str(), order, calculatedOrder); + } + pFilterBiquad->addCrossover(isLowPass, freq, qValues); + break; + } + default: + throw Error("Config(%s) - Unknown crossover sub type %d", path.c_str(), subTypeStr.c_str()); + } +} + +void Config::parseShelf(const bool isLowShelf, FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string &path) const { + const double freq = getDoubleValue(pFilterNode, "freq", path); + const double gain = getDoubleValue(pFilterNode, "gain", path); + if (pFilterNode->has("q")) { + const double q = getDoubleValue(pFilterNode, "q", path); + pFilterBiquad->addShelf(isLowShelf, freq, gain, q); + } + else { + pFilterBiquad->addShelf(isLowShelf, freq, gain); + } +} + +void Config::parsePEQ(FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string &path) const { + const double freq = getDoubleValue(pFilterNode, "freq", path); + const double q = getDoubleValue(pFilterNode, "q", path); + const double gain = getDoubleValue(pFilterNode, "gain", path); + pFilterBiquad->addPEQ(freq, q, gain); +} + +void Config::parseBandPass(FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string &path) const { + const double freq = getDoubleValue(pFilterNode, "freq", path); + const double bandwidth = getDoubleValue(pFilterNode, "bandwidth", path); + if (pFilterNode->has("gain")) { + const double gain = getDoubleValue(pFilterNode, "gain", path); + pFilterBiquad->addBandPass(freq, bandwidth, gain); + } + else { + pFilterBiquad->addBandPass(freq, bandwidth); + } +} + +void Config::parseNotch(FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string &path) const { + const double freq = getDoubleValue(pFilterNode, "freq", path); + const double bandwidth = getDoubleValue(pFilterNode, "bandwidth", path); + if (pFilterNode->has("gain")) { + const double gain = getDoubleValue(pFilterNode, "gain", path); + pFilterBiquad->addNotch(freq, bandwidth, gain); + } + else { + pFilterBiquad->addNotch(freq, bandwidth); + } +} + +void Config::parseLinkwitzTransform(FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string &path) const { + const double f0 = getDoubleValue(pFilterNode, "f0", path); + const double q0 = getDoubleValue(pFilterNode, "q0", path); + const double fp = getDoubleValue(pFilterNode, "fp", path); + const double qp = getDoubleValue(pFilterNode, "qp", path); + pFilterBiquad->addLinkwitzTransform(f0, q0, fp, qp); +} + +void Config::parseBiquad(FilterBiquad *pFilterBiquad, const JsonNode *pFilterNode, const std::string &path) const { + std::string myPath = path; + const JsonNode *pValues = getArrayNode(pFilterNode, "values", myPath); + for (size_t i = 0; i < pValues->size(); ++i) { + const JsonNode *pValueNode = pValues->get(i); + const double b0 = getDoubleValue(pValueNode, "b0", myPath); + const double b1 = getDoubleValue(pValueNode, "b1", myPath); + const double b2 = getDoubleValue(pValueNode, "b2", myPath); + const double a1 = getDoubleValue(pValueNode, "a1", myPath); + const double a2 = getDoubleValue(pValueNode, "a2", myPath); + if (pValueNode->has("a0")) { + const double a0 = getDoubleValue(pValueNode, "a0", myPath); + pFilterBiquad->add(b0, b1, b2, a0, a1, a2); + } + else { + pFilterBiquad->add(b0, b1, b2, a1, a2); + } + } +} + +void Config::parseFir(std::vector &filters, const JsonNode *pFilterNode, const std::string &path) const { + std::string myPath = path; + //Read each line in fir parameter file + const std::string filePath = getTextValue(pFilterNode, "file", myPath); + const File file(filePath); + const std::string extension = file.getExtension(); + if (extension.compare("txt") == 0) { + parseFirTxt(filters, file, myPath); + } + else if (extension.compare("wav") == 0) { + parseFirWav(filters, file, myPath); + } + else { + throw Error("Config(%s) - Unknown file extension for FIR file '%s'", myPath.c_str(), file.getPath().c_str()); + } +} + +void Config::parseFirTxt(std::vector &filters, const File &file, const std::string &path) const { + std::vector lines; + if (!file.getData(lines)) { + throw Error("Config(%s) - Can't read FIR file '%s'", path.c_str(), file.getPath().c_str()); + } + if (!lines.size()) { + throw Error("Config(%s) - FIR file '%s' is empty", path.c_str(), file.getPath().c_str()); + } + std::vector taps; + for (const std::string &str : lines) { + taps.push_back(atof(str.c_str())); + } + filters.push_back(new FilterFir(taps)); +} + +void Config::parseFirWav(std::vector &filters, const File &file, const std::string &path) const { + char *pBuffer = nullptr; + try { + //Get data + const size_t bufferSize = file.getData(&pBuffer); + WaveHeader header; + memcpy(&header, pBuffer, sizeof(header)); + const char *pData = pBuffer + sizeof(header); + if (header.numChannels != 1) { + throw Error("Config(%s) - FIR file is not mono", path.c_str()); + } + if (header.sampleRate != _sampleRate) { + throw Error("Config(%s) - FIR file sample rate doesn't match render device. Render(%d), FIR(%d)", path.c_str(), _sampleRate, header.sampleRate); + } + //Generate taps + const size_t numSamples = header.getNumSamples(); + std::vector taps; + if (header.audioFormat == WAVE_FORMAT_PCM) { + switch (header.bitsPerSample) { + case 16: + taps = Convert::pcm16ToDouble(pData, numSamples); + break; + case 24: + taps = Convert::pcm24ToDouble(pData, numSamples); + break; + case 32: + taps = Convert::pcm32ToDouble(pData, numSamples); + break; + default: + throw Error("Config(%s) - FIR file bit depth is unsupported: %u", path.c_str(), header.bitsPerSample); + } + } + else if (header.audioFormat == WAVE_FORMAT_IEEE_FLOAT) { + switch (header.bitsPerSample) { + case 32: + taps = Convert::float32ToDouble(pData, numSamples); + break; + case 64: + taps = Convert::float64ToDouble(pData, numSamples); + break; + default: + throw Error("Config(%s) - FIR file bit depth is unsupported: %u", path.c_str(), header.bitsPerSample); + } + } + else { + throw Error("Config(%s) - FIR file is in unknown audio format: %u", path.c_str(), header.audioFormat); + } + delete[] pBuffer; + filters.push_back(new FilterFir(taps)); + } + catch (const Error &e) { + delete[] pBuffer; + throw e; + } +} + +void Config::updateCrossoverMaps(const bool isLP, const int outputChannel) { + if (outputChannel > -1) { + const Channel channel = (Channel)outputChannel; + if (isLP) { + if (_addLpTo.find(channel) != _addLpTo.end()) { + _addLpTo.erase(channel); + } + } + else { + if (_addHpTo.find(channel) != _addHpTo.end()) { + _addHpTo.erase(channel); + } + } + } +} + +void Config::applyCrossoversMap(FilterBiquad *pFilterBiquad, const int outputChannel) { + if (outputChannel > -1) { + const Channel channel = (Channel)outputChannel; + const std::string basicPath = "basic"; + if (_addLpTo.find(channel) != _addLpTo.end() && _addLpTo[channel]) { + std::string lpPath = basicPath; + parseCrossover(true, pFilterBiquad, _pLpFilter, lpPath); + } + if (_addHpTo.find(channel) != _addHpTo.end() && _addHpTo[channel]) { + std::string hpPath = basicPath; + parseCrossover(false, pFilterBiquad, _pLpFilter, hpPath); + } + } +} \ No newline at end of file diff --git a/ConfigParserUtil.cpp b/ConfigParserUtil.cpp new file mode 100644 index 0000000..0b2f5f0 --- /dev/null +++ b/ConfigParserUtil.cpp @@ -0,0 +1,284 @@ +#include "Config.h" +#include "JsonNode.h" +#include "FilterType.h" +#include "SpeakerType.h" +#include "SubType.h" + +#define REF_FIELD "#ref" + +/* ***** JSON NODES ***** */ + +const JsonNode* Config::tryGetNode(const JsonNode *pNode, const std::string &field, std::string &path) const { + const JsonNode *pResult = pNode->path(field); + return _tryGetNodeInner(pResult, path, field); +} + +const JsonNode* Config::tryGetNode(const JsonNode *pNode, const size_t index, std::string &path) const { + const JsonNode *pResult = pNode->path(index); + return _tryGetNodeInner(pResult, path, std::to_string(index)); +} + +const JsonNode* Config::getNode(const JsonNode *pNode, const std::string &field, std::string &path) const { + return _getNodeInner(tryGetNode(pNode, field, path), path); +} + +const JsonNode* Config::getNode(const JsonNode *pNode, const size_t index, std::string &path) const { + return _getNodeInner(tryGetNode(pNode, index, path), path); +} + +const JsonNode* Config::_tryGetNodeInner(const JsonNode *pNode, std::string &path, const std::string &appendPath) const { + if (path.size() > 0) { + path = path + "/" + appendPath; + } + else { + path = appendPath; + } + if (pNode->has(REF_FIELD)) { + return _getReference(pNode, path); + } + return pNode; +} + +const JsonNode* Config::_getNodeInner(const JsonNode *pNode, std::string &path) const { + if (pNode->isMissingNode()) { + throw Error("Config(%s) - Field is required", path.c_str()); + } + return pNode; +} + +const JsonNode* Config::_getReference(const JsonNode *pParent, std::string &path) const { + const std::string refPath = getTextValue(pParent, REF_FIELD, path); + path = path + "/" + REF_FIELD; + + if (pParent->size() > 1) { + throw Error("Config(%s) - Can't combine reference with other fields", path.c_str()); + } + + JsonNode *pRefNode = _pJsonNode; + char buf[500]; + strcpy(buf, refPath.c_str()); + char *part = strtok(buf, "/"); + while (part != NULL) { + pRefNode = pRefNode->path(part); + part = strtok(NULL, "/"); + } + if (pRefNode->isMissingNode()) { + throw Error("Config(%s) - Can't find reference '%s'", path.c_str(), refPath.c_str()); + } + path = path + " -> " + refPath; + return pRefNode; +} + +/* ***** OBJECT NODE ***** */ + +const JsonNode* Config::tryGetObjectNode(const JsonNode *pNode, const std::string &field, std::string &path) const { + return validateObjectNode(tryGetNode(pNode, field, path), path, true); +} + +const JsonNode* Config::tryGetObjectNode(const JsonNode *pNode, const size_t index, std::string &path) const { + return validateObjectNode(tryGetNode(pNode, index, path), path, true); +} + +const JsonNode* Config::getObjectNode(const JsonNode *pNode, const std::string &field, std::string &path) const { + return validateObjectNode(getNode(pNode, field, path), path); +} + +const JsonNode* Config::getObjectNode(const JsonNode *pNode, const size_t index, std::string &path) const { + return validateObjectNode(getNode(pNode, index, path), path); +} + +const JsonNode* Config::validateObjectNode(const JsonNode *pNode, std::string &path, const bool optional) const { + if (pNode->isObject() || (pNode->isMissingNode() && optional)) { + return pNode; + } + throw Error("Config(%s) - Field is not an object. Expected: { }", path.c_str()); +} + +/* ***** ARRAY NODE ***** */ + +const JsonNode* Config::tryGetArrayNode(const JsonNode *pNode, const std::string &field, std::string &path) const { + return validateArrayNode(tryGetNode(pNode, field, path), path, true); +} + +const JsonNode* Config::tryGetArrayNode(const JsonNode *pNode, const size_t index, std::string &path) const { + return validateArrayNode(tryGetNode(pNode, index, path), path, true); +} + +const JsonNode* Config::getArrayNode(const JsonNode *pNode, const std::string &field, std::string &path) const { + return validateArrayNode(getNode(pNode, field, path), path); +} + +const JsonNode* Config::getArrayNode(const JsonNode *pNode, const size_t index, std::string &path) const { + return validateArrayNode(getNode(pNode, index, path), path); +} + +const JsonNode* Config::validateArrayNode(const JsonNode *pNode, std::string &path, const bool optional) const { + if (pNode->isArray() || (pNode->isMissingNode() && optional)) { + return pNode; + } + throw Error("Config(%s) - Field is not an array/list. Expected: [ ]", path.c_str()); +} + +/* ***** TEXT VALUE ***** */ + +const std::string Config::tryGetTextValue(const JsonNode *pNode, const std::string &field, const std::string &path) const { + std::string myPath = path; + return validateTextValue(tryGetNode(pNode, field, myPath), myPath, true); +} + +const std::string Config::tryGetTextValue(const JsonNode *pNode, const size_t index, const std::string &path) const { + std::string myPath = path; + return validateTextValue(tryGetNode(pNode, index, myPath), myPath, true); +} + +const std::string Config::getTextValue(const JsonNode *pNode, const std::string &field, const std::string &path) const { + std::string myPath = path; + return validateTextValue(getNode(pNode, field, myPath), myPath); +} + +const std::string Config::getTextValue(const JsonNode *pNode, const size_t index, const std::string &path) const { + std::string myPath = path; + return validateTextValue(getNode(pNode, index, myPath), myPath); +} + +const std::string Config::validateTextValue(const JsonNode *pNode, const std::string &path, const bool optional) const { + if (pNode->isText() || (pNode->isMissingNode() && optional)) { + return pNode->textValue(); + } + throw Error("Config(%s) - Field is not a text string. Expected: \" \"", path.c_str()); +} + +/* ***** BOOL VALUE ***** */ + +const bool Config::tryGetBoolValue(const JsonNode *pNode, const std::string &field, const std::string &path) const { + std::string myPath = path; + return validateBoolValue(tryGetNode(pNode, field, myPath), myPath, true); +} + +const bool Config::tryGetBoolValue(const JsonNode *pNode, const size_t index, const std::string &path) const { + std::string myPath = path; + return validateBoolValue(tryGetNode(pNode, index, myPath), myPath, true); +} + +const bool Config::getBoolValue(const JsonNode *pNode, const std::string &field, const std::string &path) const { + std::string myPath = path; + return validateBoolValue(getNode(pNode, field, myPath), myPath); +} + +const bool Config::getBoolValue(const JsonNode *pNode, const size_t index, const std::string &path) const { + std::string myPath = path; + return validateBoolValue(getNode(pNode, index, myPath), myPath); +} + +const bool Config::validateBoolValue(const JsonNode *pNode, const std::string &path, const bool optional) const { + if (pNode->isBoolean() || (pNode->isMissingNode() && optional)) { + return pNode->boolValue(); + } + throw Error("Config(%s) - Field is not a boolean. Expected: true/false", path.c_str()); +} + +/* ***** DOUBLE VALUE ***** */ + +const double Config::tryGetDoubleValue(const JsonNode *pNode, const std::string &field, const std::string &path) const { + std::string myPath = path; + return validateDoubleValue(tryGetNode(pNode, field, myPath), myPath, true); +} + +const double Config::tryGetDoubleValue(const JsonNode *pNode, const size_t index, const std::string &path) const { + std::string myPath = path; + return validateDoubleValue(tryGetNode(pNode, index, myPath), myPath, true); +} + +const double Config::getDoubleValue(const JsonNode *pNode, const std::string &field, const std::string &path) const { + std::string myPath = path; + return validateDoubleValue(getNode(pNode, field, myPath), myPath); +} + +const double Config::getDoubleValue(const JsonNode *pNode, const size_t index, const std::string &path) const { + std::string myPath = path; + return validateDoubleValue(getNode(pNode, index, myPath), myPath); +} + +const double Config::validateDoubleValue(const JsonNode *pNode, const std::string &path, const bool optional) const { + if (pNode->isNumber() || (pNode->isMissingNode() && optional)) { + return pNode->doubleValue(); + } + throw Error("Config(%s) - Field is not a number", path.c_str()); +} + +/* ***** INT VALUE ***** */ + +const int Config::tryGetIntValue(const JsonNode *pNode, const std::string &field, const std::string &path) const { + std::string myPath = path; + return validateIntValue(tryGetNode(pNode, field, myPath), myPath, true); +} + +const int Config::tryGetIntValue(const JsonNode *pNode, const size_t index, const std::string &path) const { + std::string myPath = path; + return validateIntValue(tryGetNode(pNode, index, myPath), myPath, true); +} + +const int Config::getIntValue(const JsonNode *pNode, const std::string &field, const std::string &path) const { + std::string myPath = path; + return validateIntValue(getNode(pNode, field, myPath), myPath); +} + +const int Config::getIntValue(const JsonNode *pNode, const size_t index, const std::string &path) const { + std::string myPath = path; + return validateIntValue(getNode(pNode, index, myPath), myPath); +} + +const int Config::validateIntValue(const JsonNode *pNode, const std::string &path, const bool optional) const { + if (pNode->isInteger() || (pNode->isMissingNode() && optional)) { + return pNode->intValue(); + } + throw Error("Config(%s) - Field is not an integer number", path.c_str()); +} + + +/* ***** ENUM ***** */ + +const SpeakerType Config::getSpeakerType(const JsonNode *pNode, const std::string &field, const std::string &path) const { + std::string textOut; + return getSpeakerType(pNode, field, textOut, path); +} + +const SpeakerType Config::getSpeakerType(const JsonNode *pNode, const std::string &field, std::string &textOut, const std::string &path) const { + const std::string str = getTextValue(pNode, field, path); + try { + return SpeakerTypes::fromString(str); + } + catch (const std::exception &e) { + throw Error("Config(%s/%s) - %s", path.c_str(), field.c_str(), e.what()); + } +} + +const FilterType Config::getFilterType(const JsonNode *pNode, const std::string &field, const std::string &path) const { + std::string textOut; + return getFilterType(pNode, field, textOut, path); +} + +const FilterType Config::getFilterType(const JsonNode *pNode, const std::string &field, std::string &textOut, const std::string &path) const { + const std::string str = getTextValue(pNode, field, path); + try { + return FilterTypes::fromString(str); + } + catch (const std::exception &e) { + throw Error("Config(%s/%s) - %s", path.c_str(), field.c_str(), e.what()); + } +} + +const SubType Config::getSubType(const JsonNode *pNode, const std::string &field, const std::string &path) const { + std::string textOut; + return getSubType(pNode, field, textOut, path); +} + +const SubType Config::getSubType(const JsonNode *pNode, const std::string &field, std::string &textOut, const std::string &path) const { + const std::string str = getTextValue(pNode, field, path); + try { + return SubTypes::fromString(str); + } + catch (const std::exception &e) { + throw Error("Config(%s/%s) - %s", path.c_str(), field.c_str(), e.what()); + } +} \ No newline at end of file diff --git a/FilterType.cpp b/FilterType.cpp index 2253744..94272d7 100644 --- a/FilterType.cpp +++ b/FilterType.cpp @@ -52,9 +52,9 @@ const std::string FilterTypes::toString(const FilterType filterType) { return "HIGH_SHELF"; case FilterType::PEQ: return "PEQ"; - case BAND_PASS: + case FilterType::BAND_PASS: return "BAND_PASS"; - case NOTCH: + case FilterType::NOTCH: return "NOTCH"; case FilterType::LINKWITZ_TRANSFORM: return "LINKWITZ_TRANSFORM"; diff --git a/FilterType.h b/FilterType.h index 9c0bd09..a78ed6a 100644 --- a/FilterType.h +++ b/FilterType.h @@ -9,14 +9,13 @@ #pragma once #include -enum FilterType { +enum class FilterType { LOW_PASS, HIGH_PASS, LOW_SHELF, HIGH_SHELF, PEQ, BAND_PASS, NOTCH, LINKWITZ_TRANSFORM, BIQUAD, FIR, CANCELLATION }; -class FilterTypes { -public: +namespace FilterTypes { - static const FilterType fromString(const std::string &str); - static const std::string toString(const FilterType filterType); + const FilterType fromString(const std::string &str); + const std::string toString(const FilterType filterType); }; \ No newline at end of file diff --git a/Input.cpp b/Input.cpp index 228a725..392eed1 100644 --- a/Input.cpp +++ b/Input.cpp @@ -1,12 +1,12 @@ #include "Input.h" -Input::Input(const std::string &name) { - _name = name; +Input::Input(const Channel channel) { + _channel = channel; _isPlaying = false; } -Input::Input(const std::string &name, const size_t out) { - _name = name; +Input::Input(const Channel channel, const Channel out) { + _channel = channel; _isPlaying = false; _routes.push_back(new Route(out)); } @@ -25,8 +25,8 @@ void Input::addRoute(Route * const pRoute) { _routes.push_back(pRoute); } -const std::string Input::getName() const { - return _name; +const Channel Input::getChannel() const { + return _channel; } void Input::evalConditions() const { diff --git a/Input.h b/Input.h index dc8d6ce..3cd280a 100644 --- a/Input.h +++ b/Input.h @@ -8,17 +8,18 @@ #pragma once #include "Route.h" +#include "Channel.h" class Input { public: - Input(const std::string &name); - Input(const std::string &name, const size_t out); + Input(const Channel channel); + Input(const Channel channel, const Channel out); ~Input(); const std::vector& getRoutes() const; void addRoute(Route * const pRoute); - const std::string getName() const; + const Channel getChannel() const; void evalConditions() const; void reset(); @@ -35,7 +36,7 @@ class Input { private: std::vector _routes; - std::string _name; + Channel _channel; bool _isPlaying; }; \ No newline at end of file diff --git a/Output.cpp b/Output.cpp index 5f9e76a..3a3bed7 100644 --- a/Output.cpp +++ b/Output.cpp @@ -1,7 +1,7 @@ #include "Output.h" -Output::Output(const std::string &name, const bool mute) { - _name = name; +Output::Output(const Channel channel, const bool mute) { + _channel = channel; _mute = mute; _clipping = 0.0; _usePostFilters = false; @@ -36,8 +36,8 @@ void Output::reset() { } } -const std::string Output::getName() const { - return _name; +const Channel Output::getChannel() const { + return _channel; } const double Output::resetClipping() { diff --git a/Output.h b/Output.h index 8d4fb9c..0ac068d 100644 --- a/Output.h +++ b/Output.h @@ -10,17 +10,18 @@ #include #include //std::max #include "Filter.h" +#include "Channel.h" class Output { public: - Output(const std::string &name, const bool mute = false); + Output(const Channel channel, const bool mute = false); ~Output(); void addFilters(const std::vector &filters); void addPostFilters(const std::vector &filters); const std::vector& getFilters() const; - const std::string getName() const; + const Channel Output::getChannel() const; void reset(); const double resetClipping(); @@ -50,7 +51,7 @@ class Output { private: std::vector _filters, _postFilters; - std::string _name; + Channel _channel; double _clipping; bool _mute, _usePostFilters; diff --git a/README.md b/README.md index 1cb926a..7ba0ecf 100644 --- a/README.md +++ b/README.md @@ -185,20 +185,6 @@ You can declare a list of your favorite filters and reuse them. } ``` -A referenced node can redefine specific fields. -```json - "outputs": { - "L": { - "delay": 2.5, - "gain": -5 - }, - "R": { - "#ref": "outputs/L" , - "gain": -7 - } - } -``` - **Conditional routing** * You can add conditions to a route. If the conditions is not met the route will not be active * For now the only condition available is to detect if an input channel is silent or not diff --git a/Route.cpp b/Route.cpp index edf1e46..908d51d 100644 --- a/Route.cpp +++ b/Route.cpp @@ -1,7 +1,8 @@ #include "Route.h" -Route::Route(const size_t channelIndex) { - _channelIndex = channelIndex; +Route::Route(const Channel channel) { + _channel = channel; + _channelIndex = (size_t)channel; _valid = true; } @@ -15,10 +16,18 @@ void Route::addFilters(const std::vector &filters) { _filters.insert(_filters.end(), filters.begin(), filters.end()); } +void Route::addFilter(Filter *pFilter) { + _filters.push_back(pFilter); +} + void Route::addCondition(const Condition &condition) { _conditions.push_back(condition); } +const Channel Route::getChannel() const { + return _channel; +} + const size_t Route::getChannelIndex() const { return _channelIndex; } diff --git a/Route.h b/Route.h index 2944803..9875b46 100644 --- a/Route.h +++ b/Route.h @@ -10,15 +10,18 @@ #include #include "Filter.h" #include "Condition.h" +#include "Channel.h" class Route { public: - Route(const size_t channelIndex); + Route(const Channel channel); ~Route(); void addFilters(const std::vector &filters); + void addFilter(Filter *pFilter); void addCondition(const Condition &condition); + const Channel getChannel() const; const size_t getChannelIndex() const; const bool hasConditions() const; const std::vector& getFilters() const; @@ -39,6 +42,7 @@ class Route { std::vector _filters; std::vector _conditions; bool _valid; + Channel _channel; size_t _channelIndex; }; \ No newline at end of file diff --git a/SpeakerType.cpp b/SpeakerType.cpp new file mode 100644 index 0000000..9638c09 --- /dev/null +++ b/SpeakerType.cpp @@ -0,0 +1,35 @@ +#include "SpeakerType.h" +#include "Error.h" +#include "Str.h" + +const SpeakerType SpeakerTypes::fromString(const std::string &strIn) { + const std::string str = String::toUpperCase(strIn); + if (str.compare("LARGE") == 0) { + return SpeakerType::LARGE; + } + else if (str.compare("SMALL") == 0) { + return SpeakerType::SMALL; + } + else if (str.compare("SUB") == 0) { + return SpeakerType::SUB; + } + else if (str.compare("OFF") == 0) { + return SpeakerType::OFF; + } + throw Error("Unknown speaker type '%s'", strIn.c_str()); +} + +const std::string SpeakerTypes::toString(const SpeakerType speakerType) { + switch (speakerType) { + case SpeakerType::LARGE: + return "LARGE"; + case SpeakerType::SMALL: + return "SMALL"; + case SpeakerType::SUB: + return "SUB"; + case SpeakerType::OFF: + return "OFF"; + default: + throw Error("Unknown speaker type %d", speakerType); + }; +} diff --git a/SpeakerType.h b/SpeakerType.h new file mode 100644 index 0000000..ab15449 --- /dev/null +++ b/SpeakerType.h @@ -0,0 +1,13 @@ +#pragma once +#include + +enum class SpeakerType { + LARGE, SMALL, SUB, OFF +}; + +namespace SpeakerTypes { + + const SpeakerType fromString(const std::string &str); + const std::string toString(const SpeakerType speakerType); + +}; diff --git a/SubType.cpp b/SubType.cpp index 908450b..263bf9a 100644 --- a/SubType.cpp +++ b/SubType.cpp @@ -16,7 +16,7 @@ const SubType SubTypes::fromString(const std::string &strIn) { else if (str.compare("CUSTOM") == 0) { return SubType::CUSTOM; } - throw Error("Unknown filter sub type '%s'", str.c_str()); + throw Error("Unknown filter sub type '%s'", strIn.c_str()); } const std::string SubTypes::toString(const SubType filterType) { diff --git a/SubType.h b/SubType.h index 73b7ab8..70be87c 100644 --- a/SubType.h +++ b/SubType.h @@ -13,10 +13,9 @@ enum class SubType { BUTTERWORTH, LINKWITZ_RILEY, BESSEL, CUSTOM }; -class SubTypes { -public: +namespace SubTypes { - static const SubType fromString(const std::string &str); - static const std::string toString(const SubType filterType); + const SubType fromString(const std::string &str); + const std::string toString(const SubType filterType); }; \ No newline at end of file diff --git a/WinDSP.vcxproj b/WinDSP.vcxproj index 5213737..d4a6578 100644 --- a/WinDSP.vcxproj +++ b/WinDSP.vcxproj @@ -96,15 +96,22 @@ + + + + + + + @@ -113,6 +120,7 @@ + @@ -122,6 +130,7 @@ + diff --git a/WinDSPLog.h b/WinDSPLog.h index 8c2f07e..5543973 100644 --- a/WinDSPLog.h +++ b/WinDSPLog.h @@ -16,8 +16,8 @@ #define LOG_NL() printf("\n"); __LOG_NL__() #else #define LOG_INFO(str, ...) printf(str, ##__VA_ARGS__); printf("\n") -#define LOG_WARN LOG_INFO -#define LOG_ERROR LOG_INFO +#define LOG_WARN(str, ...) printf(str, ##__VA_ARGS__); printf("\n\n") +#define LOG_ERROR LOG_WARN #define LOG_NL() printf("\n"); #endif diff --git a/lib/DSP/FilterDelay.cpp b/lib/DSP/FilterDelay.cpp index 3c9b46d..6a23a4c 100644 --- a/lib/DSP/FilterDelay.cpp +++ b/lib/DSP/FilterDelay.cpp @@ -2,12 +2,12 @@ #include "Constants.h" #include //lround -const int FilterDelay::getSampleDelay(const uint32_t sampleRate, double delay, const bool useUnitMeter) { +const uint32_t FilterDelay::getSampleDelay(const uint32_t sampleRate, double delay, const bool useUnitMeter) { //Value is in meter. Convert to milliseconds if (useUnitMeter) { delay = 1000.0 * delay / SPEED_OF_SOUND; } - return std::lround(sampleRate * delay / 1000.0); + return (uint32_t)std::lround(sampleRate * delay / 1000.0); } FilterDelay::FilterDelay() { @@ -19,7 +19,7 @@ FilterDelay::FilterDelay(const uint32_t sampleRate, const double delay, const bo init(getSampleDelay(sampleRate, delay, useUnitMeter)); } -FilterDelay::FilterDelay(const int sampleDelay) { +FilterDelay::FilterDelay(const uint32_t sampleDelay) { init(sampleDelay); } @@ -27,7 +27,7 @@ FilterDelay::~FilterDelay() { delete[] _pBuffer; } -void FilterDelay::init(const int sampleDelay) { +void FilterDelay::init(const uint32_t sampleDelay) { _size = sampleDelay; _index = 0; _pBuffer = new double[_size]; diff --git a/lib/DSP/FilterDelay.h b/lib/DSP/FilterDelay.h index eeff03b..0cf6284 100644 --- a/lib/DSP/FilterDelay.h +++ b/lib/DSP/FilterDelay.h @@ -6,11 +6,11 @@ class FilterDelay : public Filter { public: - static const int getSampleDelay(const uint32_t sampleRate, double delay, const bool useUnitMeter = false); + static const uint32_t getSampleDelay(const uint32_t sampleRate, double delay, const bool useUnitMeter = false); FilterDelay(); FilterDelay(const uint32_t sampleRate, const double delay, const bool useUnitMeter = false); - FilterDelay(const int sampleDelay); + FilterDelay(const uint32_t sampleDelay); ~FilterDelay(); inline const double process(const double value) override { @@ -30,6 +30,6 @@ class FilterDelay : public Filter { uint32_t _size, _index; double *_pBuffer; - void init(const int sampleDelay); + void init(const uint32_t sampleDelay); };