Compare commits

...

10 Commits

Author SHA1 Message Date
a18cefb671 add size impl 2026-03-20 14:07:38 +03:00
b7f68f97c4 add min max to result 2026-03-20 13:39:50 +03:00
cab65a93ad remove size 2026-03-20 13:02:29 +03:00
f21a17b6c9 add fft plugin 2026-03-20 10:42:25 +03:00
f904e78792 add clear for fvec 2026-03-19 14:44:25 +03:00
901c71f4eb fix name??? 2026-03-18 16:26:34 +03:00
dce15d23bd fix magnitude 2026-03-18 15:59:49 +03:00
a49fc7d5e3 rebuid struct project 2026-03-18 15:46:48 +03:00
afe54548a0 fix size func 2026-03-16 16:32:29 +03:00
84701bcfde fix size 2026-03-16 14:48:10 +03:00
16 changed files with 247 additions and 62 deletions

23
bin/main.fft.cpp Normal file
View File

@@ -0,0 +1,23 @@
#include <hack/logger/logger.hpp>
#include "harmonica.hpp"
auto main() -> int
{
// setup создается для каждого файла свой
// т.к. при чтении данных из файла уже должен быть определен размер блока
// данных для чтения m_block_size; см. установки по умолчанию.
// Передается по ссылке и заполняется необходимыми данными
hr::setup setup;
setup.m_domain = hr::DOMAIN_PLUGIN::FREQUENSY;
setup.m_file = "./sin.wav";
auto r = hr::run<hr::plugins::fft>(setup);
hack::log()("grad:", r.m_grad);
hack::log()("min:", r.m_min, "max:", r.m_max);
if (!r.empty())
{
for (auto& p : r.m_data)
hack::log()(p[0].m_values);
}
}

View File

@@ -8,16 +8,17 @@ auto main() -> int
// данных для чтения m_block_size; см. установки по умолчанию.
// Передается по ссылке и заполняется необходимыми данными
hr::setup setup;
// setup.m_domain = hr::DOMAIN_PLUGIN::FREQUENSY;
setup.m_domain = hr::DOMAIN_PLUGIN::TIME;
setup.m_file = "./sin.wav";
auto r = hr::run<hr::plugins::raw_data>(setup);
hack::log()("size:", r.m_data.size());
hack::log()("grad:", r.m_grad);
hack::log()("min:", r.m_min, "max:", r.m_max);
hack::log()("size", r.m_size);
// if (!r.empty())
// {
// std::vector<float> s;
// for (auto p : r.m_data) s.push_back(p.m_value[0]);
// hack::log()(s);
// }
if (!r.empty())
{
for (auto& p : r.m_data)
hack::log()(p[0].m_value);
}
}

View File

@@ -1,6 +1,6 @@
executable(
meson.project_name(),
'main.cpp',
'main.raw_data.cpp',
dependencies : deps,
cpp_args: args,
include_directories : inc

View File

@@ -11,6 +11,7 @@
#include "plugins/raw_data/raw_data.hpp" // IWYU pragma: keep
#include "plugins/magnitude/magnitude.hpp" // IWYU pragma: keep
#include "plugins/fft/fft.hpp" // IWYU pragma: keep
namespace hr
{
@@ -77,7 +78,7 @@ namespace hr
}
// Подготовка к следующей итерации
read = hack::math::min(length, static_cast<uint_t>(floorf(read_length + .5)));
read = hack::math::min(length, static_cast<uint_t>(std::floorf(read_length + .5)));
// Дополнение буфера нулями если считано неполный блок (конец файла)
if (in.size() > read) std::fill(in.begin() + read, in.end(), 0.0);
@@ -86,9 +87,8 @@ namespace hr
real_time timestamp = real_time::frame2rt(read, sf_info.samplerate);
ad.process(in, timestamp);
}
while (read == setup.m_step_size); // Продолжать пока читаются полные блоки
while (read == setup.m_step_size);
// Закрытие файла и возврат результата
sf_close(file);
return ad.get_result();
}

View File

@@ -16,6 +16,7 @@ headers = [
# plugins
'plugins/raw_data/raw_data.hpp',
'plugins/magnitude/magnitude.hpp',
'plugins/fft/fft.hpp',
'harmonica.hpp'
]
@@ -30,6 +31,7 @@ sources = [
# plugins
'plugins/raw_data/raw_data.cpp',
'plugins/magnitude/magnitude.cpp',
'plugins/fft/fft.cpp',
]
lib = library(

52
src/plugins/fft/fft.cpp Normal file
View File

@@ -0,0 +1,52 @@
#include "fft.hpp"
#include "utils/var.hpp"
#include "utils/math.hpp"
namespace hr::plugins
{
fft::fft(const setup& st) : plugin{ st }
{
m_setup.m_plugin_name = "FFT";
m_setup.m_plugin_description = "Вычисляет FFT и прокидывает данные дальше в вашу программу.";
GUARD_DOMAIN(FREQUENSY);
// Данные - амплитуда частот
m_result.init(1);
// заполняем градацию по частотам
// тут не нужно делить на 2. получаем = 513 исходя из базового m_setup
// т.к. реализация FFT (rdft) уже возвращает только уникальную часть спектра, а не полный симметричный массив из 1024 элементов.
m_frames = m_setup.m_step_size + 1;
m_result.m_grad.reserve(m_frames);
m_result.m_size = m_frames;
for (size_t i = 0; i < m_frames; ++i)
m_result.m_grad.push_back(static_cast<float>(i) * m_setup.m_sample_rate / m_setup.m_block_size);
}
void fft::process(fvec_t& base, real_time timestamp)
{
}
void fft::process(cvec_t& fft, fvec_t& base, real_time timestamp)
{
result::bit b;
b.m_name = "Amplitudes";
b.m_duration = timestamp;
b.m_values.reserve(m_frames);
for (size_t i = 0; i < m_frames; ++i)
{
// Конвертация в децибелы
auto v = fft.m_norm[i];
if (v > 0.000001f) v = 20.0f * log10(v);
else v = -120.0f; // Минимальное значение для логарифмической шкалы
b.m_values.push_back(v);
m_result.set_min_max(v);
}
m_result.set_bit(0, b);
}
result fft::get_result()
{
return m_result;
}
}

22
src/plugins/fft/fft.hpp Normal file
View File

@@ -0,0 +1,22 @@
#pragma once
#include "utils/workers/plugin.hpp"
namespace hr::plugins
{
class fft : public plugin
{
public:
fft(const setup& st);
virtual ~fft() = default;
private:
result m_result;
std::size_t m_frames;
public:
void process(fvec_t& base, real_time timestamp) override;
void process(cvec_t& fft, fvec_t& base, real_time timestamp) override;
result get_result() override;
};
}

View File

@@ -1,9 +1,16 @@
#include "magnitude.hpp"
#include "utils/var.hpp"
namespace hr::plugins
{
magnitude::magnitude(const setup& st) : plugin{ st }
{
m_setup.m_plugin_name = "Magnitude";
m_setup.m_plugin_description = "Средняя амплитуда спектра (общая энергия сигнала).";
GUARD_DOMAIN(FREQUENSY);
m_result.init(1);
}
void magnitude::process(fvec_t& base, real_time timestamp)
@@ -13,15 +20,20 @@ namespace hr::plugins
void magnitude::process(cvec_t& fft, fvec_t& base, real_time timestamp)
{
std::size_t frames = fft.size();
base_t m = 0.f;
for (std::size_t i = 0; i < frames; ++i) m += fft.m_norm[i];
m /= frames;
base_t v = 0.f;
for (std::size_t i = 0; i < frames; ++i) v += fft.m_norm[i];
v /= frames;
m_result.m_max = hack::math::max(m_result.m_max, v);
m_result.m_min = hack::math::min(m_result.m_min, v);
result::bit b;
b.m_value.push_back(m);
b.m_value = v;
b.m_duration = timestamp;
m_result.set_bit(b);
++m_result.m_size;
m_result.set_bit(0, b);
m_result.set_min_max(v);
}
result magnitude::get_result()

View File

@@ -1,23 +1,33 @@
#include "raw_data.hpp"
#include "utils/var.hpp"
namespace hr::plugins
{
// Этот плагин ни чего не делает и предназначен при сохранении единственности интерфейса просто
// передавать сырые необработанные данные. Например дял отрисовки базового сигнала.
// передавать сырые необработанные данные. Например для отрисовки базового сигнала.
// Он не работает в частотной области
raw_data::raw_data(const setup& st) : plugin{ st }
{
if (st.m_domain != DOMAIN_PLUGIN::TIME)
hack::error()("Этот плагин работает только во временной области!");
m_setup.m_plugin_name = "Raw Data";
m_setup.m_plugin_description = "Ни чего не вычисляет, просто прокидывает сырые данные дальше в вашу программу, для сохранения единой концепции.";
GUARD_DOMAIN(TIME);
// Только 1 тип данных будет в этом плагине, а имеено
// сырые данные из файла
m_result.init(1);
}
void raw_data::process(fvec_t& base, real_time timestamp)
{
for (auto& v : base)
{
result::bit b;
b.m_value = base;
b.m_value = v;
b.m_duration = timestamp;
m_tmp.set_bit(b);
m_size += base.size();
m_result.set_bit(0, b);
m_result.set_min_max(v);
}
m_result.m_size += base.size();
}
void raw_data::process(cvec_t& fft, fvec_t& base, real_time timestamp)
@@ -26,23 +36,6 @@ namespace hr::plugins
result raw_data::get_result()
{
if (m_tmp.m_data.empty())
return m_result;
m_result.m_data.reserve(m_size);
std::size_t index = 0;
for (auto& t : m_tmp.m_data)
{
for (auto s : t.m_value)
{
result::bit b;
b.m_value.push_back(s);
b.m_duration = t.m_duration;
m_result.set_bit(b);
}
}
return m_result;
}
}

View File

@@ -12,8 +12,6 @@ namespace hr::plugins
private:
result m_result;
result m_tmp;
std::size_t m_size = 0;
public:
void process(fvec_t& base, real_time timestamp) override;

View File

@@ -41,6 +41,12 @@ namespace hr
m_data.reserve(size);
}
// очищает данные с сохранением выделенной под них памяти
void fvec_t::clear()
{
m_data.clear();
}
// Циклический сдвиг вектора: первая половина меняется местами со второй
// Пример: [1,2,3,4,5,6] -> [4,5,6,1,2,3]
// Для нечетных размеров: [1,2,3,4,5,6,7] -> [5,6,7,1,2,3,4]

View File

@@ -37,6 +37,7 @@ namespace hr
void resize(std::size_t new_size, const base_t el);
void reserve(std::size_t size);
void shift();
void clear();
private:
std::vector<base_t> m_data;

23
src/utils/math.hpp Normal file
View File

@@ -0,0 +1,23 @@
#pragma once
#include <type_traits>
#include <cmath>
namespace hr
{
template<typename T>
T log10(T value)
{
if constexpr (std::is_same_v<T, float>)
return std::log10f(value);
else if constexpr (std::is_same_v<T, double>)
return std::log10(value);
else if constexpr (std::is_same_v<T, long double>)
return std::log10l(value);
else
{
static_assert(std::is_same_v<T, float> || std::is_same_v<T, double> || std::is_same_v<T, long double>, "Unsupported type for log10");
return std::log10(value); // fallback
}
}
}

View File

@@ -1,3 +1,11 @@
#pragma once
namespace hr { }
#define GUARD_DOMAIN(x) if (m_setup.m_domain != DOMAIN_PLUGIN::x) \
{\
hack::error()("plugin name:", m_setup.m_plugin_name);\
hack::warn()("Этот плагин работает только в " #x " области!"); \
std::terminate();\
}
namespace hr {
}

View File

@@ -1,9 +1,10 @@
#pragma once
#include <vector>
#include "utils/real_time/real_time.hpp"
#include "utils/fvec/fvec.hpp"
#include <hack/math/math.hpp>
#include <hack/logger/logger.hpp>
#include "utils/real_time/real_time.hpp"
#include "utils/using.hpp"
namespace hr
{
@@ -11,29 +12,67 @@ namespace hr
{
struct bit
{
std::string m_name;
real_time m_duration;
fvec_t m_value;
// когда в расчете только одно значение. да может быть и нужно это использовать как
// массив с индексом 0 типа m_values[0], но как-то вот так.
// Потому как отрисовка графиков тот еще праздник...
// конечно в боевой задаче это можно и нужно оптимизировать, но в данном случае для вывода на экран и понимания процесса
// это можно опустить и использовать так как есть...
base_t m_value;
// когда у тебя получается на один бин большоймассив данных, типа расчет fft (см. комент выше)
std::vector<base_t> m_values;
};
void set_bit(bit& b)
void set_bit(std::size_t index, bit& b)
{
m_data.push_back(b);
try
{
m_data.at(index).push_back(b);
}
catch(std::exception& e)
{
hack::error()("Хрень какая-то с массивом данных");
hack::warn()(e.what());
}
}
bool empty() const
{
bool res = true;
try
{
if (!m_data.empty()) res = m_data.at(0).m_value.empty();
}
catch(std::exception& e)
{
hack::error()(e.what());
}
return res;
return m_data.empty();
}
std::vector<bit> m_data;
// инициализирует кол-во данных для расчета в плагине
// см. ниже
void init(std::size_t count_data)
{
for (std::size_t i = 0; i < count_data; ++i)
m_data.push_back(std::vector<bit>{});
}
// схема такая:
// Первый вектор - кол-во типов замеров, например 7 т.е можно выстроить 7 линий на графике
// если захотелось увидеть их
// Второй вектор - данные для каждой линии, т.е. именно сими биты
std::vector<std::vector<bit>> m_data;
// максимальные и минимальные элементы в принципе в данном расчете
// в основном нужны для графической реализации
// соответственно метод ниже в попощь
base_t m_max = std::numeric_limits<base_t>::min();
base_t m_min = std::numeric_limits<base_t>::max();
void set_min_max(base_t v)
{
m_max = hack::math::max(m_max, v);
m_min = hack::math::min(m_min, v);
}
// в данном случае вы сами решаете, что для вас значит размер
// это может быть размер всего массива m_data или размер массива данных хранящихся в каждом бине
// или этот же массив но помноженный на размер m_data
std::size_t m_size = 0;
// иногда нужна градуировка одна и тажа для всех бинов
std::vector<base_t> m_grad;
};
}

View File

@@ -18,9 +18,14 @@ namespace hr
int m_channels;
std::filesystem::path m_file;
// Количество семплов, которые обрабатываются за один раз
std::size_t m_block_size = 1'024;
// На сколько семплов сдвигается окно при следующей обработке
std::size_t m_step_size = 512;
std::string m_plugin_name;
std::string m_plugin_description;
DOMAIN_PLUGIN m_domain = DOMAIN_PLUGIN::TIME;
};
}