[EGD-8162] Allow importing audio profile settings from json file

This allows for imporing the audio profile setitngs, including
EQ settings from the json file, thus allowing modification of
those setitngs without the need of recompiling the source code.
This commit is contained in:
Bartosz Cichocki
2022-01-25 14:44:32 +01:00
committed by Bartosz Cichocki
parent 6aa54d4ad5
commit 81052bbcb1
24 changed files with 760 additions and 152 deletions

View File

@@ -0,0 +1,46 @@
{
"samplerate": 16000,
"bitWidth": 16,
"flags": 5,
"outputVolume": 1,
"inputGain": 0,
"inputPath": 1,
"outputPath": 1,
"filterParams": [
{
"filterType": "HighPass",
"frequency": 700,
"samplerate": 44100,
"Q": 0.701,
"gain": 10
},
{
"filterType": "LowPass",
"frequency": 4993.7,
"samplerate": 44100,
"Q": 0.701,
"gain": -5
},
{
"filterType": "LowPass",
"frequency": 6000,
"samplerate": 44100,
"Q": 0.701,
"gain": 10
},
{
"filterType": "HighPass",
"frequency": 100.4,
"samplerate": 44100,
"Q": 0.701,
"gain": 10
},
{
"filterType": "Notch",
"frequency": 1500.7,
"samplerate": 44100,
"Q": 0.701,
"gain": -3
}
]
}

View File

@@ -0,0 +1,46 @@
{
"samplerate": 0,
"bitWidth": 16,
"flags": 0,
"outputVolume": 0,
"inputGain": 0,
"inputPath": 2,
"outputPath": 0,
"filterParams": [
{
"filterType": "HighPass",
"frequency": 100.2,
"samplerate": 44100,
"Q": 0.701,
"gain": 0
},
{
"filterType": "LowPass",
"frequency": 17996.2,
"samplerate": 44100,
"Q": 0.701,
"gain": 0
},
{
"filterType": "HighShelf",
"frequency": 13984.7,
"samplerate": 44100,
"Q": 0.701,
"gain": -10
},
{
"filterType": "LowShelf",
"frequency": 200.4,
"samplerate": 44100,
"Q": 0.701,
"gain": -10
},
{
"filterType": "None",
"frequency": 1496.7,
"samplerate": 44100,
"Q": 0.701,
"gain": -4
}
]
}

View File

@@ -0,0 +1,46 @@
{
"samplerate": 0,
"bitWidth": 16,
"flags": 0,
"outputVolume": 0,
"inputGain": 0,
"inputPath": 2,
"outputPath": 0,
"filterParams": [
{
"filterType": "HighPass",
"frequency": 997,
"samplerate": 44100,
"Q": 0.701,
"gain": 0
},
{
"filterType": "LowPass",
"frequency": 4993.7,
"samplerate": 44100,
"Q": 0.701,
"gain": 0
},
{
"filterType": "None",
"frequency": 13984.7,
"samplerate": 44100,
"Q": 0.701,
"gain": -10
},
{
"filterType": "None",
"frequency": 200.4,
"samplerate": 44100,
"Q": 0.701,
"gain": -10
},
{
"filterType": "None",
"frequency": 1496.7,
"samplerate": 44100,
"Q": 0.701,
"gain": -4
}
]
}

View File

@@ -0,0 +1,46 @@
{
"samplerate": 0,
"bitWidth": 16,
"flags": 0,
"outputVolume": 1,
"inputGain": 0,
"inputPath": 2,
"outputPath": 2,
"filterParams": [
{
"filterType": "HighPass",
"frequency": 501.8,
"samplerate": 44100,
"Q": 0.701,
"gain": 0
},
{
"filterType": "LowPass",
"frequency": 14999.5,
"samplerate": 44100,
"Q": 0.701,
"gain": 0
},
{
"filterType": "HighShelf",
"frequency": 15975.7,
"samplerate": 44100,
"Q": 0.701,
"gain": -10
},
{
"filterType": "LowShelf",
"frequency": 401,
"samplerate": 44100,
"Q": 0.701,
"gain": -10
},
{
"filterType": "Parametric",
"frequency": 1496.7,
"samplerate": 44100,
"Q": 0.701,
"gain": -4
}
]
}

View File

@@ -0,0 +1,46 @@
{
"samplerate": 16000,
"bitWidth": 16,
"flags": 5,
"outputVolume": 1,
"inputGain": 0,
"inputPath": 1,
"outputPath": 2,
"filterParams": [
{
"filterType": "HighPass",
"frequency": 307.3,
"samplerate": 16000,
"Q": 0.701,
"gain": 0
},
{
"filterType": "LowPass",
"frequency": 5080.1,
"samplerate": 16000,
"Q": 0.847,
"gain": 0
},
{
"filterType": "None",
"frequency": 15975.7,
"samplerate": 16000,
"Q": 0.701,
"gain": -10
},
{
"filterType": "None",
"frequency": 200.4,
"samplerate": 16000,
"Q": 0.701,
"gain": -10
},
{
"filterType": "None",
"frequency": 1496.7,
"samplerate": 44100,
"Q": 0.701,
"gain": -4
}
]
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "Profile.hpp"
@@ -21,6 +21,7 @@
#include "ProfileRoutingBluetoothHFP.hpp"
#include "ProfileRecordingBluetoothHFP.hpp"
#include "ProfileConfigUtils.hpp"
#include <Utils.hpp>
namespace audio
@@ -94,6 +95,22 @@ namespace audio
: audioConfiguration(fmt), audioDeviceType(devType), name(name), type(type)
{}
Profile::Profile(const std::string &name,
const Type type,
const std::filesystem::path &configurationPath,
const audio::codec::Configuration &fallbackConfig,
AudioDevice::Type devType)
: audioDeviceType(devType), name(name), type(type)
{
try {
audioConfiguration = loadConfigurationFromFile(configurationPath);
}
catch (std::invalid_argument &e) {
LOG_ERROR("Failed loading the profile configuration from file, using fallback! Cause: %s", e.what());
audioConfiguration = fallbackConfig;
}
}
void Profile::SetInputGain(Gain gain)
{
audioConfiguration.inputGain = gain;
@@ -131,4 +148,5 @@ namespace audio
}
return utils::enumToString(profileType);
}
} // namespace audio

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
@@ -9,6 +9,8 @@
#include <memory>
#include <functional>
#include <string>
#include <filesystem>
#include <module-vfs/paths/include/purefs/filesystem_paths.hpp>
namespace audio
{
@@ -128,6 +130,12 @@ namespace audio
const audio::codec::Configuration &fmt,
AudioDevice::Type devType);
Profile(const std::string &name,
const Type type,
const std::filesystem::path &configurationPath,
const audio::codec::Configuration &fallbackConfig,
AudioDevice::Type devType);
audio::codec::Configuration audioConfiguration;
AudioDevice::Type audioDeviceType = AudioDevice::Type::Audiocodec;

View File

@@ -0,0 +1,108 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "ProfileConfigUtils.hpp"
#include <fstream>
#include <log/log.hpp>
#include <json11.hpp>
#include <magic_enum.hpp>
namespace audio
{
namespace strings
{
constexpr inline auto samplerate = "samplerate";
constexpr inline auto bitWidth = "bitWidth";
constexpr inline auto flags = "flags";
constexpr inline auto outputVolume = "outputVolume";
constexpr inline auto outputPath = "outputPath";
constexpr inline auto inputGain = "inputGain";
constexpr inline auto inputPath = "inputPath";
constexpr inline auto filterParams = "filterParams";
constexpr inline auto filterType = "filterType";
constexpr inline auto frequency = "frequency";
constexpr inline auto Q = "Q";
constexpr inline auto gain = "gain";
} // namespace strings
namespace utils
{
template <typename E, typename T>
constexpr inline typename std::enable_if<std::is_enum<E>::value && std::is_integral<T>::value, E>::type toEnum(
T value) noexcept
{
return static_cast<E>(value);
}
equalizer::FilterType toFilterType(const std::string &filterName)
{
auto filterType = magic_enum::enum_cast<equalizer::FilterType>(filterName);
if (filterType.has_value()) {
return filterType.value();
}
else {
LOG_ERROR("Unknown filter type, using none");
return equalizer::FilterType::None;
}
}
const std::string readFileToString(std::filesystem::path filePath)
{
std::ifstream file;
std::string configString;
LOG_DEBUG("Reading %s ...", filePath.c_str());
file.open(filePath);
if (not file.is_open()) {
LOG_ERROR("Can't open profile configuration file, using defaults!");
throw std::invalid_argument("Can't open file!");
}
while (file) {
std::string line;
std::getline(file, line);
configString += line;
}
file.close();
return configString;
}
} // namespace utils
const audio::codec::Configuration loadConfigurationFromFile(std::filesystem::path filePath)
{
auto configString = utils::readFileToString(filePath);
audio::codec::Configuration config;
json11::Json configJson;
std::string err;
configJson = json11::Json::parse(configString.c_str(), err);
if (!err.empty()) {
LOG_ERROR("Failed parsing device string!");
throw std::invalid_argument("Can't parse the file!");
}
config.sampleRate_Hz = configJson[strings::samplerate].int_value();
config.bitWidth = configJson[strings::bitWidth].int_value();
config.flags = configJson[strings::flags].int_value();
config.outputVolume = configJson[strings::outputVolume].number_value();
config.inputGain = configJson[strings::inputGain].number_value();
config.inputPath = utils::toEnum<codec::InputPath>(configJson[strings::inputPath].int_value());
config.outputPath = utils::toEnum<codec::OutputPath>(configJson[strings::outputPath].int_value());
json11::Json::array paramsArray;
audio::equalizer::Equalizer filterParams;
paramsArray = configJson[strings::filterParams].array_items();
for (size_t i = 0; i < equalizer::bands; i++) {
auto filterType = utils::toFilterType(paramsArray[i][strings::filterType].string_value());
auto frequency = paramsArray[i][strings::frequency].number_value();
auto samplerate = paramsArray[i][strings::samplerate].int_value();
auto Q = paramsArray[i][strings::Q].number_value();
auto gain = paramsArray[i][strings::gain].number_value();
filterParams.at(i) = qfilter_CalculateCoeffs(filterType, frequency, samplerate, Q, gain);
}
config.filterCoefficients = filterParams;
return config;
}
} // namespace audio

View File

@@ -0,0 +1,13 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
#include <filesystem>
#include <Audio/codec.hpp>
namespace audio
{
[[nodiscard]] const audio::codec::Configuration loadConfigurationFromFile(std::filesystem::path filePath);
const std::string readFile(std::filesystem::path filePath);
} // namespace audio

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
@@ -10,30 +10,30 @@ namespace audio
class ProfilePlaybackHeadphones : public Profile
{
public:
ProfilePlaybackHeadphones(Volume volume)
explicit ProfilePlaybackHeadphones(Volume volume)
: Profile(
"Playback Headphones",
Type::PlaybackHeadphones,
purefs::dir::getUserDiskPath() / "data/equalizer/headphones_playback.json",
audio::codec::Configuration{
.sampleRate_Hz = 0,
.bitWidth = 16,
.flags = 0,
.outputVolume = static_cast<float>(volume),
.outputVolume = 0,
.inputGain = 0,
.inputPath = audio::codec::InputPath::None,
.outputPath = audio::codec::OutputPath::Headphones,
.filterCoefficients =
{qfilter_CalculateCoeffs(
audio::equalizer::FilterType::FilterHighPass, 100.2f, 44100, 0.701f, 0),
{qfilter_CalculateCoeffs(audio::equalizer::FilterType::HighPass, 100.2f, 44100, 0.701f, 0),
qfilter_CalculateCoeffs(audio::equalizer::FilterType::LowPass, 17996.2f, 44100, 0.701f, 0),
qfilter_CalculateCoeffs(
audio::equalizer::FilterType::FilterLowPass, 17996.2f, 44100, 0.701f, 0),
qfilter_CalculateCoeffs(
audio::equalizer::FilterType::FilterHighShelf, 13984.7f, 44100, 0.701f, -10),
qfilter_CalculateCoeffs(
audio::equalizer::FilterType::FilterLowShelf, 200.4f, 44100, 0.701f, -10),
qfilter_CalculateCoeffs(audio::equalizer::FilterType::FilterNone, 0, 44100, 0.701f, -4)}},
audio::equalizer::FilterType::HighShelf, 13984.7f, 44100, 0.701f, -10),
qfilter_CalculateCoeffs(audio::equalizer::FilterType::LowShelf, 200.4f, 44100, 0.701f, -10),
qfilter_CalculateCoeffs(audio::equalizer::FilterType::None, 0, 44100, 0.701f, -4)}},
AudioDevice::Type::Audiocodec)
{}
{
audioConfiguration.outputVolume = static_cast<float>(volume);
}
};
} // namespace audio

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
@@ -11,30 +11,31 @@ namespace audio
class ProfilePlaybackLoudspeaker : public Profile
{
public:
ProfilePlaybackLoudspeaker(Volume volume)
: Profile("Playback Loudspeaker",
Type::PlaybackLoudspeaker,
audio::codec::Configuration{
.sampleRate_Hz = 0,
.bitWidth = 16,
.flags = 0,
.outputVolume = static_cast<float>(volume),
.inputGain = 0,
.inputPath = audio::codec::InputPath::None,
.outputPath = audio::codec::OutputPath::Loudspeaker,
.filterCoefficients =
{qfilter_CalculateCoeffs(
audio::equalizer::FilterType::FilterHighPass, 501.8f, 44100, 0.701f, 0),
qfilter_CalculateCoeffs(
audio::equalizer::FilterType::FilterLowPass, 14999.5f, 44100, 0.701f, 0),
qfilter_CalculateCoeffs(
audio::equalizer::FilterType::FilterHighShelf, 15975.7f, 44100, 0.701f, -10),
qfilter_CalculateCoeffs(
audio::equalizer::FilterType::FilterLowShelf, 401.f, 44100, 0.701f, -10),
qfilter_CalculateCoeffs(
audio::equalizer::FilterType::FilterParametric, 1496.7f, 44100, 0.701f, -4)}},
AudioDevice::Type::Audiocodec)
{}
explicit ProfilePlaybackLoudspeaker(Volume volume)
: Profile(
"Playback Loudspeaker",
Type::PlaybackLoudspeaker,
purefs::dir::getUserDiskPath() / "data/equalizer/loudspeaker_playback.json",
audio::codec::Configuration{
.sampleRate_Hz = 0,
.bitWidth = 16,
.flags = 0,
.outputVolume = 0,
.inputGain = 0,
.inputPath = audio::codec::InputPath::None,
.outputPath = audio::codec::OutputPath::Loudspeaker,
.filterCoefficients =
{qfilter_CalculateCoeffs(audio::equalizer::FilterType::HighPass, 501.8f, 44100, 0.701f, 0),
qfilter_CalculateCoeffs(audio::equalizer::FilterType::LowPass, 14999.5f, 44100, 0.701f, 0),
qfilter_CalculateCoeffs(
audio::equalizer::FilterType::HighShelf, 15975.7f, 44100, 0.701f, -10),
qfilter_CalculateCoeffs(audio::equalizer::FilterType::LowShelf, 401.f, 44100, 0.701f, -10),
qfilter_CalculateCoeffs(
audio::equalizer::FilterType::Parametric, 1496.7f, 44100, 0.701f, -4)}},
AudioDevice::Type::Audiocodec)
{
audioConfiguration.outputVolume = static_cast<float>(volume);
}
};
} // namespace audio

View File

@@ -11,31 +11,31 @@ namespace audio
{
public:
ProfileRoutingEarspeaker(Volume volume, Gain gain)
: Profile("Routing Earspeaker",
Type::RoutingEarspeaker,
audio::codec::Configuration{
.sampleRate_Hz = 16000,
.bitWidth = 16,
.flags = static_cast<uint32_t>(
audio::codec::Flags::InputLeft) | // microphone use left audio channel
static_cast<uint32_t>(audio::codec::Flags::OutputMono),
.outputVolume = static_cast<float>(volume),
.inputGain = static_cast<float>(gain),
.inputPath = audio::codec::InputPath::Microphone,
.outputPath = audio::codec::OutputPath::Earspeaker,
.filterCoefficients =
{qfilter_CalculateCoeffs(
audio::equalizer::FilterType::FilterHighPass, 700.f, 44100, 0.701f, 10),
qfilter_CalculateCoeffs(
audio::equalizer::FilterType::FilterLowPass, 4993.7f, 44100, 0.701f, -5),
qfilter_CalculateCoeffs(
audio::equalizer::FilterType::FilterLowPass, 6000.7f, 44100, 0.701f, 10),
qfilter_CalculateCoeffs(
audio::equalizer::FilterType::FilterHighPass, 100.4f, 44100, 0.701f, 10),
qfilter_CalculateCoeffs(
audio::equalizer::FilterType::FilterNotch, 1500.7f, 44100, 0.701f, -3)}},
AudioDevice::Type::Audiocodec)
{}
: Profile(
"Routing Earspeaker",
Type::RoutingEarspeaker,
purefs::dir::getUserDiskPath() / "data/equalizer/earspeaker_routing.json",
audio::codec::Configuration{
.sampleRate_Hz = 16000,
.bitWidth = 16,
.flags =
static_cast<uint32_t>(audio::codec::Flags::InputLeft) | // microphone use left audio channel
static_cast<uint32_t>(audio::codec::Flags::OutputMono),
.outputVolume = 0,
.inputGain = 0,
.inputPath = audio::codec::InputPath::Microphone,
.outputPath = audio::codec::OutputPath::Earspeaker,
.filterCoefficients =
{qfilter_CalculateCoeffs(audio::equalizer::FilterType::HighPass, 700.f, 44100, 0.701f, 10),
qfilter_CalculateCoeffs(audio::equalizer::FilterType::LowPass, 4993.7f, 44100, 0.701f, -5),
qfilter_CalculateCoeffs(audio::equalizer::FilterType::LowPass, 6000.7f, 44100, 0.701f, 10),
qfilter_CalculateCoeffs(audio::equalizer::FilterType::HighPass, 100.4f, 44100, 0.701f, 10),
qfilter_CalculateCoeffs(audio::equalizer::FilterType::Notch, 1500.7f, 44100, 0.701f, -3)}},
AudioDevice::Type::Audiocodec)
{
audioConfiguration.outputVolume = static_cast<float>(volume);
audioConfiguration.inputGain = static_cast<float>(gain);
}
};
} // namespace audio

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
@@ -11,30 +11,27 @@ namespace audio
{
public:
ProfileRoutingHeadphones(Volume volume, Gain gain)
: Profile("Routing Headset",
Type::RoutingHeadphones,
audio::codec::Configuration{
.sampleRate_Hz = 16000,
.bitWidth = 16,
.flags = static_cast<uint32_t>(
audio::codec::Flags::InputLeft) | // microphone use left audio channel
static_cast<uint32_t>(audio::codec::Flags::OutputMono),
.outputVolume = static_cast<float>(volume),
.inputGain = static_cast<float>(gain),
.inputPath = audio::codec::InputPath::Headphones,
.outputPath = audio::codec::OutputPath::Headphones,
.filterCoefficients =
{qfilter_CalculateCoeffs(
audio::equalizer::FilterType::FilterHighPass, 997.f, 44100, 0.701f, 0),
qfilter_CalculateCoeffs(
audio::equalizer::FilterType::FilterLowPass, 4993.7f, 44100, 0.701f, 0),
qfilter_CalculateCoeffs(
audio::equalizer::FilterType::FilterNone, 15975.7f, 44100, 0.701f, -10),
qfilter_CalculateCoeffs(
audio::equalizer::FilterType::FilterNone, 200.4f, 44100, 0.701f, -10),
qfilter_CalculateCoeffs(
audio::equalizer::FilterType::FilterNone, 1496.7f, 44100, 0.701f, -4)}},
AudioDevice::Type::Audiocodec)
: Profile(
"Routing Headset",
Type::RoutingHeadphones,
purefs::dir::getUserDiskPath() / "data/equalizer/headphones_routing.json",
audio::codec::Configuration{
.sampleRate_Hz = 16000,
.bitWidth = 16,
.flags =
static_cast<uint32_t>(audio::codec::Flags::InputLeft) | // microphone use left audio channel
static_cast<uint32_t>(audio::codec::Flags::OutputMono),
.outputVolume = static_cast<float>(volume),
.inputGain = static_cast<float>(gain),
.inputPath = audio::codec::InputPath::Headphones,
.outputPath = audio::codec::OutputPath::Headphones,
.filterCoefficients =
{qfilter_CalculateCoeffs(audio::equalizer::FilterType::HighPass, 997.f, 44100, 0.701f, 0),
qfilter_CalculateCoeffs(audio::equalizer::FilterType::LowPass, 4993.7f, 44100, 0.701f, 0),
qfilter_CalculateCoeffs(audio::equalizer::FilterType::None, 15975.7f, 44100, 0.701f, -10),
qfilter_CalculateCoeffs(audio::equalizer::FilterType::None, 200.4f, 44100, 0.701f, -10),
qfilter_CalculateCoeffs(audio::equalizer::FilterType::None, 1496.7f, 44100, 0.701f, -4)}},
AudioDevice::Type::Audiocodec)
{}
};

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
@@ -13,31 +13,36 @@ namespace audio
public:
ProfileRoutingLoudspeaker(Volume volume, Gain gain)
: Profile("Routing Speakerphone",
Type::RoutingLoudspeaker,
audio::codec::Configuration{
.sampleRate_Hz = sampleRate,
.bitWidth = 16,
.flags = static_cast<uint32_t>(
audio::codec::Flags::InputLeft) | // microphone use left audio channel
static_cast<uint32_t>(audio::codec::Flags::OutputMono),
.outputVolume = static_cast<float>(volume),
.inputGain = static_cast<float>(gain),
.inputPath = audio::codec::InputPath::Microphone,
.outputPath = audio::codec::OutputPath::Loudspeaker,
.filterCoefficients =
{qfilter_CalculateCoeffs(
audio::equalizer::FilterType::FilterHighPass, 307.3f, sampleRate, 0.701f, 0),
qfilter_CalculateCoeffs(
audio::equalizer::FilterType::FilterLowPass, 5080.1f, sampleRate, 0.847f, 0),
qfilter_CalculateCoeffs(
audio::equalizer::FilterType::FilterNone, 15975.7f, sampleRate, 0.701f, -10),
qfilter_CalculateCoeffs(
audio::equalizer::FilterType::FilterNone, 200.4f, sampleRate, 0.701f, -10),
qfilter_CalculateCoeffs(
audio::equalizer::FilterType::FilterNone, 1496.7f, sampleRate, 0.701f, -4)}},
AudioDevice::Type::Audiocodec)
{}
: Profile(
"Routing Speakerphone",
Type::RoutingLoudspeaker,
purefs::dir::getUserDiskPath() / "data/equalizer/loudspeaker_routing.json",
audio::codec::Configuration{
.sampleRate_Hz = sampleRate,
.bitWidth = 16,
.flags =
static_cast<uint32_t>(audio::codec::Flags::InputLeft) | // microphone use left audio channel
static_cast<uint32_t>(audio::codec::Flags::OutputMono),
.outputVolume = 0,
.inputGain = 0,
.inputPath = audio::codec::InputPath::Microphone,
.outputPath = audio::codec::OutputPath::Loudspeaker,
.filterCoefficients = {qfilter_CalculateCoeffs(
audio::equalizer::FilterType::HighPass, 307.3f, sampleRate, 0.701f, 0),
qfilter_CalculateCoeffs(
audio::equalizer::FilterType::LowPass, 5080.1f, sampleRate, 0.847f, 0),
qfilter_CalculateCoeffs(
audio::equalizer::FilterType::None, 15975.7f, sampleRate, 0.701f, -10),
qfilter_CalculateCoeffs(
audio::equalizer::FilterType::None, 200.4f, sampleRate, 0.701f, -10),
qfilter_CalculateCoeffs(
audio::equalizer::FilterType::None, 1496.7f, sampleRate, 0.701f, -4)}},
AudioDevice::Type::Audiocodec)
{
audioConfiguration.sampleRate_Hz = sampleRate;
audioConfiguration.outputVolume = static_cast<float>(volume);
audioConfiguration.inputGain = static_cast<float>(gain);
}
};
} // namespace audio

View File

@@ -0,0 +1,51 @@
# Profile JSON file
This document aims to describe the profile file and how to tweak those values.
## First things first
The JSON files describing the profile parameters are used to configure the audio profile with proper input/output and filter settings.
It is **NOT** recommended to change anything than the filter settings as it might make your device speechless.
According to the [MAX98090 datasheet](https://datasheets.maximintegrated.com/en/ds/MAX98090.pdf), it is possible to have 7-band-EQ, although we're
using only 5-band-EQ right now. It can be easily changed via proper register setup. All filters are realized using the [biquad filters](https://en.wikipedia.org/wiki/Digital_biquad_filter) (second order IIR filter).
In order to prevent audio issues, when no file is present or the JSON structure is damaged, the fallback config is being loaded.
##File structure
| Field | Value type | Description |
|--------------|------------|-------------------------------------------------------------|
| samplerate | integer | Defines the sample rate of the profile |
| bitWidth | integer | Defines the bit width of the audio stream |
| flags | integer | Defines the input/output channels (see codec.hpp) |
| outputVolume | float | Defines the output volume for the particular profile |
| inputGain | float | Defines the input gain for the particular profile |
| inputPath | integer | Defines the audio path for profile's input (see codec.hpp) |
| outputPath | integer | Defines the audio path for profile's output (see codec.hpp) |
| filterParams | array | Array of filter parameters (one per band) |
###Filter parameters array
The filter parameters array consists of a one JSON struct per filter in the codec. It means that adding next element in the array
does not give any effect, but removing one of them will lead to wrong audio configuration! If you want to disable unused filter, select
appropriate filter type - `None`.
###Array element structure
| Field | Value type | Description |
|------------|------------|------------------------------------------------------------|
| filterType | string | Defines the type of the filter |
| frequency | float | Defines the cutoff frequency for the filter |
| samplerate | integer | Defines the samplerate used to calculate the filter coeffs |
| Q | float | Defines Q-factor for the filter |
| gain | float | Defines gain for shelf-like filters |
Filter type can be selected from following types:
* `LowPass` - simple low pass filter
* `HighPass` - simple high pass filter
* `HighShelf` - shelving high pass filter
* `LowShelf` - shelving low pass filter
* `Notch` - notch filter
* `Parametric` - parametric filter
* `Flat` - flat transfer function filter
* `None` - filter is disabled

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
@@ -42,11 +42,11 @@ namespace audio::codec
InputPath inputPath = InputPath::None;
OutputPath outputPath = OutputPath::None;
audio::equalizer::Equalizer filterCoefficients = {
qfilter_CalculateCoeffs(audio::equalizer::FilterType::FilterNone, 100.2f, 44100, 0.701f, 0),
qfilter_CalculateCoeffs(audio::equalizer::FilterType::FilterNone, 17996.2f, 44100, 0.701f, 0),
qfilter_CalculateCoeffs(audio::equalizer::FilterType::FilterNone, 13984.7f, 44100, 0.701f, -10),
qfilter_CalculateCoeffs(audio::equalizer::FilterType::FilterNone, 200.4f, 44100, 0.701f, -10),
qfilter_CalculateCoeffs(audio::equalizer::FilterType::FilterNone, 0, 44100, 0.701f, -4)};
qfilter_CalculateCoeffs(audio::equalizer::FilterType::None, 100.2f, 44100, 0.701f, 0),
qfilter_CalculateCoeffs(audio::equalizer::FilterType::None, 17996.2f, 44100, 0.701f, 0),
qfilter_CalculateCoeffs(audio::equalizer::FilterType::None, 13984.7f, 44100, 0.701f, -10),
qfilter_CalculateCoeffs(audio::equalizer::FilterType::None, 200.4f, 44100, 0.701f, -10),
qfilter_CalculateCoeffs(audio::equalizer::FilterType::None, 0, 44100, 0.701f, -4)};
};
} // namespace audio::codec

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "Equalizer.hpp"
@@ -15,10 +15,10 @@ namespace audio::equalizer
constexpr auto qMinValue = .1f;
constexpr auto qMaxValue = 10.f;
constexpr auto frequencyMinValue = 0.f;
if (frequency < frequencyMinValue && filter != FilterType::FilterNone) {
if (frequency < frequencyMinValue && filter != FilterType::None) {
throw std::invalid_argument("Negative frequency provided");
}
if ((Q < qMinValue || Q > qMaxValue) && filter != FilterType::FilterNone) {
if ((Q < qMinValue || Q > qMaxValue) && filter != FilterType::None) {
throw std::invalid_argument("Q out of range");
}
QFilterCoefficients filter_coeff;
@@ -30,7 +30,7 @@ namespace audio::equalizer
float gain_abs = pow(10, gain / 40);
switch (filter) {
case FilterType::FilterBandPass:
case FilterType::BandPass:
filter_coeff.b0 = alpha;
filter_coeff.b1 = 0;
filter_coeff.b2 = -alpha;
@@ -39,7 +39,7 @@ namespace audio::equalizer
a0 = 1 + alpha;
break;
case FilterType::FilterHighPass:
case FilterType::HighPass:
filter_coeff.b0 = (1 + cs) / 2;
filter_coeff.b1 = -(1 + cs);
filter_coeff.b2 = (1 + cs) / 2;
@@ -48,7 +48,7 @@ namespace audio::equalizer
a0 = 1 + alpha;
break;
case FilterType::FilterLowPass:
case FilterType::LowPass:
filter_coeff.b0 = (1 - cs) / 2;
filter_coeff.b1 = 1 - cs;
filter_coeff.b2 = (1 - cs) / 2;
@@ -56,7 +56,7 @@ namespace audio::equalizer
filter_coeff.a2 = 1 - alpha;
a0 = 1 + alpha;
break;
case FilterType::FilterFlat:
case FilterType::Flat:
filter_coeff.b0 = 0.0;
filter_coeff.b1 = 0.0;
filter_coeff.b2 = 1;
@@ -64,7 +64,7 @@ namespace audio::equalizer
filter_coeff.a2 = 0.0;
a0 = 1;
break;
case FilterType::FilterNotch:
case FilterType::Notch:
filter_coeff.b0 = 1;
filter_coeff.b1 = -2 * cs;
filter_coeff.b2 = 1;
@@ -72,7 +72,7 @@ namespace audio::equalizer
filter_coeff.a2 = 1 - alpha;
a0 = 1 + alpha;
break;
case FilterType::FilterHighShelf:
case FilterType::HighShelf:
filter_coeff.b0 = gain_abs * ((gain_abs + 1) + (gain_abs - 1) * cs + 2 * sqrt(gain_abs) * alpha);
filter_coeff.b1 = -2 * gain_abs * ((gain_abs - 1.0) + (gain_abs + 1) * cs);
filter_coeff.b2 = gain_abs * ((gain_abs + 1) + (gain_abs - 1) * cs - 2 * sqrt(gain_abs) * alpha);
@@ -80,7 +80,7 @@ namespace audio::equalizer
filter_coeff.a1 = 2 * ((gain_abs - 1) - (gain_abs + 1) * cs);
filter_coeff.a2 = (gain_abs + 1) - (gain_abs - 1) * cs - 2 * sqrt(gain_abs) * alpha;
break;
case FilterType::FilterLowShelf:
case FilterType::LowShelf:
filter_coeff.b0 = gain_abs * ((gain_abs + 1) - (gain_abs - 1) * cs + 2 * sqrt(gain_abs) * alpha);
filter_coeff.b1 = 2 * gain_abs * ((gain_abs - 1.0) - (gain_abs + 1) * cs);
filter_coeff.b2 = gain_abs * ((gain_abs + 1) - (gain_abs - 1) * cs - 2 * sqrt(gain_abs) * alpha);
@@ -88,7 +88,7 @@ namespace audio::equalizer
filter_coeff.a1 = -2 * ((gain_abs - 1) + (gain_abs + 1) * cs);
filter_coeff.a2 = (gain_abs + 1) + (gain_abs - 1) * cs - 2 * sqrt(gain_abs) * alpha;
break;
case FilterType::FilterParametric:
case FilterType::Parametric:
filter_coeff.b0 = 1.0 + alpha * gain_abs;
filter_coeff.b1 = -2.0 * cs;
filter_coeff.b2 = 1.0 - alpha * gain_abs;
@@ -96,7 +96,7 @@ namespace audio::equalizer
filter_coeff.a1 = -2.0 * cs;
filter_coeff.a2 = 1.0 - alpha / gain_abs;
break;
case FilterType::FilterNone:
case FilterType::None:
filter_coeff.b0 = 1;
filter_coeff.b1 = 0;
filter_coeff.b2 = 0;

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
@@ -8,6 +8,11 @@
namespace audio::equalizer
{
/*
* Currently we have 5 band EQ configured in the codec chip, although it is possible
* to use up to 7 band EQ - it is configurable via internal chip registers.
*/
constexpr inline auto bands = 5;
struct QFilterCoefficients
{
float b0;
@@ -15,21 +20,59 @@ namespace audio::equalizer
float b2;
float a1;
float a2;
inline bool operator==(const QFilterCoefficients &rhs) const
{
if (b0 != rhs.b0) {
return false;
}
if (b1 != rhs.b1) {
return false;
}
if (b2 != rhs.b2) {
return false;
}
if (a1 != rhs.a1) {
return false;
}
if (a2 != rhs.a2) {
return false;
}
return true;
}
inline bool operator!=(const QFilterCoefficients &rhs) const
{
if (b0 != rhs.b0) {
return true;
}
if (b1 != rhs.b1) {
return true;
}
if (b2 != rhs.b2) {
return true;
}
if (a1 != rhs.a1) {
return true;
}
if (a2 != rhs.a2) {
return true;
}
return false;
}
};
using Equalizer = std::array<QFilterCoefficients, 5>;
using Equalizer = std::array<QFilterCoefficients, bands>;
enum class FilterType
{
FilterBandPass,
FilterHighPass,
FilterLowPass,
FilterFlat,
FilterNotch,
FilterLowShelf,
FilterHighShelf,
FilterParametric,
FilterNone
BandPass,
HighPass,
LowPass,
Flat,
Notch,
LowShelf,
HighShelf,
Parametric,
None
};
QFilterCoefficients qfilter_CalculateCoeffs(

View File

@@ -51,4 +51,14 @@ add_catch2_executable(
module-utils
)
add_catch2_executable(
NAME
audio-config-utils
SRCS
unittest_config_utils.cpp
LIBS
module-audio
module-utils
)
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/testfiles" DESTINATION "${CMAKE_BINARY_DIR}")

View File

@@ -0,0 +1,46 @@
{
"samplerate": 44100,
"bitWidth": 8,
"flags": 1,
"outputVolume": 1,
"inputGain": 2,
"inputPath": 2,
"outputPath": 3,
"filterParams": [
{
"filterType": "None",
"frequency": 1000.2,
"samplerate": 44100,
"Q": 0.7,
"gain": 10
},
{
"filterType": "HighPass",
"frequency": 2000.0,
"samplerate": 8000,
"Q": 1.7,
"gain": -10
},
{
"filterType": "LowPass",
"frequency": 10000,
"samplerate": 44100,
"Q": 0.75,
"gain": 2.5
},
{
"filterType": "Notch",
"frequency": 2500,
"samplerate": 44100,
"Q": 4.4,
"gain": 5.3
},
{
"filterType": "BandPass",
"frequency": 1000,
"samplerate": 44100,
"Q": 0.7,
"gain": 10
}
]
}

View File

@@ -0,0 +1,75 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#define CATCH_CONFIG_MAIN
#include "catch2/catch.hpp"
#include <Audio/Profiles/ProfileConfigUtils.hpp>
#include <purefs/filesystem_paths.hpp>
TEST_CASE("QFilterCoefficients - overloaded operator test")
{
audio::equalizer::QFilterCoefficients coeffs1{1, 1, 1, 1, 1}, coeffs2{1, 1, 1, 1, 1};
SECTION("Equals")
{
REQUIRE(coeffs1 == coeffs2);
}
SECTION("Differs 1/5")
{
coeffs1.b0 = 2;
REQUIRE(coeffs1 != coeffs2);
}
SECTION("Differs 2/5")
{
coeffs2.b1 = 2;
REQUIRE(coeffs1 != coeffs2);
}
SECTION("Differs 3/5")
{
coeffs1.a1 = 2;
REQUIRE(coeffs1 != coeffs2);
}
SECTION("Differs 4/5")
{
coeffs2.a2 = 2;
REQUIRE(coeffs1 != coeffs2);
}
SECTION("Differs 5/5")
{
coeffs2.b2 = 2;
REQUIRE(coeffs1 != coeffs2);
}
}
TEST_CASE("Audio profile config utils")
{
SECTION("Loading config from json file")
{
auto config = audio::loadConfigurationFromFile("testfiles/testProfile.json");
REQUIRE(config.sampleRate_Hz == 44100);
REQUIRE(config.bitWidth == 8);
REQUIRE(config.flags == 1);
REQUIRE(config.inputGain == 2.0);
REQUIRE(config.outputVolume == 1.0);
REQUIRE(config.inputPath == audio::codec::InputPath::None);
REQUIRE(config.outputPath == audio::codec::OutputPath::None);
audio::equalizer::Equalizer filterCoefficients = {
qfilter_CalculateCoeffs(audio::equalizer::FilterType::None, 1000.2f, 44100, 0.7f, 10),
qfilter_CalculateCoeffs(audio::equalizer::FilterType::HighPass, 2000.0f, 8000, 1.7f, -10),
qfilter_CalculateCoeffs(audio::equalizer::FilterType::LowPass, 10000.0f, 44100, 0.75f, 2.5),
qfilter_CalculateCoeffs(audio::equalizer::FilterType::Notch, 2500.0f, 44100, 4.4f, 5.3),
qfilter_CalculateCoeffs(audio::equalizer::FilterType::BandPass, 1000, 44100, 0.7f, 10)};
for (size_t i = 0; i < audio::equalizer::bands; i++) {
REQUIRE(config.filterCoefficients.at(i) == filterCoefficients.at(i));
}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#define CATCH_CONFIG_MAIN
@@ -14,7 +14,7 @@ SCENARIO("Calculate filter coeff")
GIVEN("High pass filter")
{
const auto filterHighPass = qfilter_CalculateCoeffs(FilterType::FilterHighPass, 300.9f, 44100, 0.701f, 0);
const auto filterHighPass = qfilter_CalculateCoeffs(FilterType::HighPass, 300.9f, 44100, 0.701f, 0);
THEN("Registers 1,2,3 should match b0 setup")
{
const auto [byte1, byte2, byte3] = utils::floatingPointConverter(filterHighPass.b0);
@@ -53,7 +53,7 @@ SCENARIO("Calculate filter coeff")
}
GIVEN("High shelf filter")
{
const auto filterHighShelf = qfilter_CalculateCoeffs(FilterType::FilterLowShelf, 401.f, 44100, 0.701f, -10);
const auto filterHighShelf = qfilter_CalculateCoeffs(FilterType::LowShelf, 401.f, 44100, 0.701f, -10);
THEN("Registers 1,2 should match b0 setup")
{
const auto [byte1, byte2, _] = utils::floatingPointConverter(filterHighShelf.b0);
@@ -88,7 +88,7 @@ SCENARIO("Calculate filter coeff")
GIVEN("Filter none")
{
const auto filterNone = qfilter_CalculateCoeffs(FilterType::FilterNone, 0, 0, 0, 0);
const auto filterNone = qfilter_CalculateCoeffs(FilterType::None, 0, 0, 0, 0);
THEN("Register 1 should be equal to 16. Registers 2,3 should be equal to 0")
{
const auto [byte1, byte2, byte3] = utils::floatingPointConverter(filterNone.b0);
@@ -132,7 +132,7 @@ SCENARIO("Calculate filter coeff")
{
THEN("Calculation of coefficients should throw")
{
REQUIRE_THROWS_AS(qfilter_CalculateCoeffs(FilterType::FilterHighPass, 300.9f, 44100, -1.f, 0),
REQUIRE_THROWS_AS(qfilter_CalculateCoeffs(FilterType::HighPass, 300.9f, 44100, -1.f, 0),
std::invalid_argument);
}
}
@@ -140,7 +140,7 @@ SCENARIO("Calculate filter coeff")
{
THEN("Calculation of coefficients should throw")
{
REQUIRE_THROWS_AS(qfilter_CalculateCoeffs(FilterType::FilterHighPass, 300.9f, 44100, 100.f, 0),
REQUIRE_THROWS_AS(qfilter_CalculateCoeffs(FilterType::HighPass, 300.9f, 44100, 100.f, 0),
std::invalid_argument);
}
}
@@ -150,7 +150,7 @@ SCENARIO("Calculate filter coeff")
{
THEN("Calculation of coefficients should throw")
{
REQUIRE_THROWS_AS(qfilter_CalculateCoeffs(FilterType::FilterHighPass, -300.9f, 44100, 0.2f, 0),
REQUIRE_THROWS_AS(qfilter_CalculateCoeffs(FilterType::HighPass, -300.9f, 44100, 0.2f, 0),
std::invalid_argument);
}
}

View File

@@ -35,6 +35,7 @@ target_sources(
${CMAKE_CURRENT_SOURCE_DIR}/Audio/Operation/RecorderOperation.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Audio/Operation/RouterOperation.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Audio/Profiles/Profile.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Audio/Profiles/ProfileConfigUtils.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Audio/ServiceObserver.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Audio/Stream.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Audio/StreamFactory.cpp

View File

@@ -117,7 +117,9 @@ In order to store `Operation` configuration a concept of `Audio Profile` has bee
Profiles configurations can be found in directory: [Profiles](./Audio/Profiles)
`Operations` may not implement support for all possible `Profile` parameters i.e. `inputGain` and `inputPath` will be ignored in playback `Operation`.
**IMPORTANT:** For the time being profiles are not loaded and stored into database. This should be fixed.
~~**IMPORTANT:** For the time being profiles are not loaded and stored into database. This should be fixed.~~
**NOTE:** Currently some of the profiles are configurable via json files located [here](../image/user/data/equalizer). The json format explanation can be found [here](./Audio/Profiles/README.md).
**IMPORTANT:** Callbacks mechanism is only experimental and should be considered as incomplete.
# Audio class