add rec to audio and base modern concepts
This commit is contained in:
@@ -2,12 +2,15 @@
|
|||||||
|
|
||||||
* Это очень нужная коллекция фрагментов рабочего кода для возможного упрощения жизни.*
|
* Это очень нужная коллекция фрагментов рабочего кода для возможного упрощения жизни.*
|
||||||
|
|
||||||
Автор не претендует на чистоту, реализации и верность исполнения, поэтому, если вы думаете, что можете сделать это по-другому, то пожалуйста, сделайте это.
|
Автор не претендует на чистоту, реализации и верность исполнения, поэтому если вы думаете, что можете сделать это по-другому, то пожалуйста, сделайте это.
|
||||||
|
|
||||||
Пожалуйста, смотрите пример реализации в /bin/main.cpp и tests/...
|
Пожалуйста, смотрите пример реализации в /bin/имя_желаемого.cpp и tests/...
|
||||||
|
|
||||||
Что тут:
|
Что тут:
|
||||||
|
- audio - набор методов по работе со звуком: воспроизведение, запись, работа с массивом звуков и т.п.
|
||||||
- concepts - набор разнообразных реализаций концептов
|
- concepts - набор разнообразных реализаций концептов
|
||||||
|
|
||||||
|
|
||||||
- iterators - набор разнообразных реализаций итераторов
|
- iterators - набор разнообразных реализаций итераторов
|
||||||
- log - реализация лдогирования
|
- log - реализация лдогирования
|
||||||
- patterns - набор различных паттернов проектирования
|
- patterns - набор различных паттернов проектирования
|
||||||
|
|||||||
41
bin/audio.cpp
Normal file
41
bin/audio.cpp
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#include "hack/audio/generate.hpp"
|
||||||
|
#include "hack/audio/play.hpp"
|
||||||
|
#include "hack/audio/save.hpp"
|
||||||
|
#include "hack/audio/record.hpp"
|
||||||
|
|
||||||
|
#include "hack/logger/logger.hpp"
|
||||||
|
|
||||||
|
auto main(int argc, char *argv[]) -> int
|
||||||
|
{
|
||||||
|
hack::warn()("Пример работы: audio");
|
||||||
|
|
||||||
|
int sample_rate = 44100;
|
||||||
|
|
||||||
|
// созданиен массива звуков
|
||||||
|
std::vector<double> frequencies = { 130.81, 138.59, 146.83, 155.56, 164.81, 174.61, 185.00, 196.00, 207.65, 220.00, 233.08, 246.94 };
|
||||||
|
std::vector<double> melody;
|
||||||
|
for (double freq : frequencies)
|
||||||
|
{
|
||||||
|
auto note = hack::audio::generate::sine(freq, 0.3, sample_rate);
|
||||||
|
melody.insert(melody.end(), note.begin(), note.end());
|
||||||
|
// Добавляем небольшую паузу между нотами. Т.е. вставляем кол-во с нулевым значением.
|
||||||
|
melody.insert(melody.end(), sample_rate * 0.05, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
hack::log()("Воспроизведение последовательности нот из массива");
|
||||||
|
hack::audio::play(melody, sample_rate);
|
||||||
|
|
||||||
|
hack::log()("Запись последовательности нот масива в файл");
|
||||||
|
std::string file = "/mnt/raid/projects/hack/hack/bin/test/note.wav";
|
||||||
|
hack::audio::save(file, melody, sample_rate);
|
||||||
|
|
||||||
|
hack::log()("Воспроизведение последовательности нот из файла");
|
||||||
|
hack::audio::play(file);
|
||||||
|
|
||||||
|
hack::log()("Запись аудио с микрофона");
|
||||||
|
std::vector<double> recorded_data;
|
||||||
|
hack::audio::record(recorded_data, sample_rate, 1, 5.0);
|
||||||
|
|
||||||
|
hack::log()("Воспроизведение аудио с микрофона");
|
||||||
|
hack::audio::play(recorded_data, sample_rate, 1);
|
||||||
|
}
|
||||||
24
bin/main.cpp
24
bin/main.cpp
@@ -1,10 +1,6 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <forward_list>
|
#include <forward_list>
|
||||||
|
|
||||||
#include "hack/audio/generate.hpp"
|
|
||||||
#include "hack/audio/play.hpp"
|
|
||||||
#include "hack/audio/save.hpp"
|
|
||||||
|
|
||||||
#include "hack/logger/logger.hpp"
|
#include "hack/logger/logger.hpp"
|
||||||
|
|
||||||
#include "hack/range/sort.hpp"
|
#include "hack/range/sort.hpp"
|
||||||
@@ -18,27 +14,15 @@
|
|||||||
|
|
||||||
auto main(int argc, char *argv[]) -> int
|
auto main(int argc, char *argv[]) -> int
|
||||||
{
|
{
|
||||||
|
// HERE
|
||||||
|
// concepts
|
||||||
{
|
{
|
||||||
int sample_rate = 44100;
|
|
||||||
// Воспроизведение последовательности нот
|
|
||||||
hack::warn()("Пример использования: audio");
|
|
||||||
|
|
||||||
std::vector<double> frequencies = { 130.81, 138.59, 146.83, 155.56, 164.81, 174.61, 185.00, 196.00, 207.65, 220.00, 233.08, 246.94 };
|
|
||||||
std::vector<double> melody;
|
|
||||||
for (double freq : frequencies)
|
|
||||||
{
|
|
||||||
auto note = hack::audio::generate::sine(freq, 0.3, sample_rate);
|
|
||||||
melody.insert(melody.end(), note.begin(), note.end());
|
|
||||||
// Добавляем небольшую паузу между нотами. Т.е. вставляем кол-во с нулевым значением.
|
|
||||||
melody.insert(melody.end(), sample_rate * 0.05, 0.0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hack::log()("Воспроизведение последовательности нот...");
|
|
||||||
hack::audio::play(melody, sample_rate);
|
|
||||||
|
|
||||||
hack::log()("Запись последовательности нот в файл");
|
|
||||||
hack::audio::save("/mnt/raid/projects/hack/hack/bin/test/note.wav", melody, sample_rate);
|
|
||||||
}
|
|
||||||
|
|
||||||
// patterns::ring_buffer
|
// patterns::ring_buffer
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
executable(
|
executable(
|
||||||
meson.project_name(),
|
meson.project_name(),
|
||||||
'main.cpp',
|
'audio.cpp',
|
||||||
dependencies : deps,
|
dependencies : deps,
|
||||||
cpp_args: args,
|
cpp_args: args,
|
||||||
include_directories : inc
|
include_directories : inc
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
// Генерация разнообразных звуковых волн
|
// Генерация разнообразных звуковых волн.
|
||||||
namespace hack::audio::generate
|
namespace hack::audio::generate
|
||||||
{
|
{
|
||||||
// Генерация синусоидального сигнала
|
// Генерация синусоидального сигнала.
|
||||||
inline std::vector<double> sine(double frequency, double duration, int sample_rate)
|
inline std::vector<double> sine(double frequency, double duration, int sample_rate)
|
||||||
{
|
{
|
||||||
std::vector<double> samples;
|
std::vector<double> samples;
|
||||||
@@ -22,7 +22,7 @@ namespace hack::audio::generate
|
|||||||
return samples;
|
return samples;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Генерация прямоугольного сигнала
|
// Генерация прямоугольного сигнала.
|
||||||
inline std::vector<double> square_wave(double frequency, double duration, int sample_rate)
|
inline std::vector<double> square_wave(double frequency, double duration, int sample_rate)
|
||||||
{
|
{
|
||||||
std::vector<double> samples;
|
std::vector<double> samples;
|
||||||
@@ -38,7 +38,7 @@ namespace hack::audio::generate
|
|||||||
return samples;
|
return samples;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Генерация белого шума
|
// Генерация белого шума.
|
||||||
inline std::vector<double> white_noise(double duration, int sample_rate)
|
inline std::vector<double> white_noise(double duration, int sample_rate)
|
||||||
{
|
{
|
||||||
std::vector<double> samples;
|
std::vector<double> samples;
|
||||||
@@ -47,7 +47,7 @@ namespace hack::audio::generate
|
|||||||
for (int i = 0; i < total_samples; ++i)
|
for (int i = 0; i < total_samples; ++i)
|
||||||
{
|
{
|
||||||
double sample = (static_cast<double>(rand()) / RAND_MAX) * 2.0 - 1.0;
|
double sample = (static_cast<double>(rand()) / RAND_MAX) * 2.0 - 1.0;
|
||||||
samples.push_back(sample * 0.3); // Уменьшаем громкость
|
samples.push_back(sample * 0.3); // Уменьшаем громкость.
|
||||||
}
|
}
|
||||||
|
|
||||||
return samples;
|
return samples;
|
||||||
|
|||||||
3
src/hack/audio/info.md
Normal file
3
src/hack/audio/info.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Вся работа с аудио на данный момент крутиться во круг wav.
|
||||||
|
Т.е. что-то типа - воспроизвести/записать в mp3 или что-то подобное требует своей реализации, которой
|
||||||
|
буду заниматься по мерее её необходимости !!!
|
||||||
55
src/hack/audio/manager.hpp
Normal file
55
src/hack/audio/manager.hpp
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <portaudio.h>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace hack::audio
|
||||||
|
{
|
||||||
|
// Данный класс, является реализацией безопастного отключения аудио-устройства
|
||||||
|
// вашего компа и убирает лишнюю писанину типа Pa_Terminate() и естественного отслеживания таких вызовов.
|
||||||
|
// Также он типа потоко-безопасен.
|
||||||
|
class pa_manager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static pa_manager& instance()
|
||||||
|
{
|
||||||
|
static pa_manager pam;
|
||||||
|
return pam;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool initialize()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock { m_mutex };
|
||||||
|
if (!m_initialized)
|
||||||
|
{
|
||||||
|
PaError err = Pa_Initialize();
|
||||||
|
if (err != paNoError) return false;
|
||||||
|
m_initialized = true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void terminate()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_mutex);
|
||||||
|
if (m_initialized)
|
||||||
|
{
|
||||||
|
Pa_Terminate();
|
||||||
|
m_initialized = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~pa_manager()
|
||||||
|
{
|
||||||
|
terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
pa_manager() : m_initialized { false } {}
|
||||||
|
pa_manager(const pa_manager&) = delete;
|
||||||
|
pa_manager& operator=(const pa_manager&) = delete;
|
||||||
|
|
||||||
|
bool m_initialized;
|
||||||
|
std::mutex m_mutex;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,53 +1,86 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <portaudio.h>
|
#include <sndfile.hh>
|
||||||
|
#include "hack/audio/manager.hpp"
|
||||||
#include "hack/logger/logger.hpp"
|
#include "hack/logger/logger.hpp"
|
||||||
|
|
||||||
namespace hack::audio
|
namespace hack::audio
|
||||||
{
|
{
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
const int FRAMES_PER_BUFFER = 256;
|
// Типичные значения, используемые в индустрии:
|
||||||
|
// FRAMES_PER_BUFFER = 256; Более требовательный к CPU
|
||||||
|
// FRAMES_PER_BUFFER = 512; Оптимальный баланс
|
||||||
|
// FRAMES_PER_BUFFER = 1024; Большая задержка, но минимальная нагрузка
|
||||||
|
const int FRAMES_PER_BUFFER = 512;
|
||||||
|
|
||||||
// callback - вызывается, когда нужны новые аудио-данные
|
struct array_data
|
||||||
static int callback(const void*, void* output_buffer, unsigned long frames, const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags, void* data)
|
|
||||||
{
|
{
|
||||||
auto& samples = *static_cast<std::vector<double>*>(data);
|
std::vector<double> samples;
|
||||||
static size_t pos = 0;
|
size_t position = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct file_data
|
||||||
|
{
|
||||||
|
SndfileHandle file;
|
||||||
|
std::vector<double> buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Колбэк для воспроизведения из памяти.
|
||||||
|
static int array_callback(const void*, void* output_buffer, unsigned long frames, const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags, void* data)
|
||||||
|
{
|
||||||
|
auto* audio_data = static_cast<array_data*>(data);
|
||||||
auto* out = static_cast<float*>(output_buffer);
|
auto* out = static_cast<float*>(output_buffer);
|
||||||
|
|
||||||
for (unsigned long i = 0; i < frames; ++i)
|
for (unsigned long i = 0; i < frames; ++i)
|
||||||
{
|
{
|
||||||
// передаем даннеы если есть, иначе - тишина.
|
if (audio_data->position < audio_data->samples.size())
|
||||||
if (pos < samples.size()) out[i] = static_cast<float>(samples[pos++]);
|
out[i] = static_cast<float>(audio_data->samples[audio_data->position++]);
|
||||||
else out[i] = 0.0f;
|
else
|
||||||
|
out[i] = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (pos >= samples.size()) ? paComplete : paContinue;
|
return (audio_data->position >= audio_data->samples.size()) ? paComplete : paContinue;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// пока только wav
|
// Колбэк для воспроизведения из файла.
|
||||||
inline void play(std::vector<double>& samples, int sample_rate)
|
static int file_callback(const void*, void* output_buffer, unsigned long frames, const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags, void* data)
|
||||||
{
|
{
|
||||||
PaError err = Pa_Initialize();
|
auto* fd = static_cast<file_data*>(data);
|
||||||
if (err != paNoError)
|
auto* out = static_cast<float*>(output_buffer);
|
||||||
|
|
||||||
|
sf_count_t frames_read = fd->file.readf(fd->buffer.data(), frames);
|
||||||
|
|
||||||
|
if (frames_read > 0)
|
||||||
{
|
{
|
||||||
hack::error()("PortAudio error: ", Pa_GetErrorText(err));
|
for (sf_count_t i = 0; i < frames_read * fd->file.channels(); ++i)
|
||||||
|
out[i] = static_cast<float>(fd->buffer[i]);
|
||||||
|
return paContinue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return paComplete;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Общая функция инициализации и воспроизведения.
|
||||||
|
template<typename DataType, typename CallbackType>
|
||||||
|
void play_impl(DataType& data, CallbackType callback, int sample_rate, int channels)
|
||||||
|
{
|
||||||
|
if (!pa_manager::instance().initialize())
|
||||||
|
{
|
||||||
|
hack::error()("Failed to initialize PortAudio");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// открытие потока
|
|
||||||
PaStream* stream;
|
PaStream* stream;
|
||||||
err = Pa_OpenDefaultStream(&stream,
|
PaError err = Pa_OpenDefaultStream(&stream,
|
||||||
0, // Нет входных каналов
|
0, // нет входных каналов
|
||||||
1, // 1 выходной канал (моно)
|
channels, // количество каналов
|
||||||
paFloat32, // 32-bit floating-point output
|
paFloat32, // 32-bit floating-point
|
||||||
sample_rate,
|
sample_rate,
|
||||||
FRAMES_PER_BUFFER,
|
FRAMES_PER_BUFFER,
|
||||||
callback,
|
callback,
|
||||||
&samples);
|
&data);
|
||||||
|
|
||||||
if (err != paNoError)
|
if (err != paNoError)
|
||||||
{
|
{
|
||||||
@@ -56,21 +89,42 @@ namespace hack::audio
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// запуск потока
|
|
||||||
err = Pa_StartStream(stream);
|
err = Pa_StartStream(stream);
|
||||||
if (err != paNoError)
|
if (err != paNoError)
|
||||||
{
|
{
|
||||||
std::cerr << "PortAudio error: " << Pa_GetErrorText(err) << std::endl;
|
std::cerr << "PortAudio error: " << Pa_GetErrorText(err) << std::endl;
|
||||||
Pa_CloseStream(stream);
|
Pa_CloseStream(stream);
|
||||||
Pa_Terminate();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ждём окончания воспроизведения
|
while (Pa_IsStreamActive(stream)) Pa_Sleep(100);
|
||||||
while (Pa_IsStreamActive(stream)) Pa_Sleep(100); // Пауза 100 мс между проверками
|
|
||||||
|
|
||||||
// очистка
|
|
||||||
Pa_CloseStream(stream);
|
Pa_CloseStream(stream);
|
||||||
Pa_Terminate();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void play(const std::vector<double>& samples, int sample_rate, int channels = 1)
|
||||||
|
{
|
||||||
|
array_data audio_data { samples };
|
||||||
|
play_impl(audio_data, array_callback, sample_rate, channels);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void play(const std::string& filename)
|
||||||
|
{
|
||||||
|
file_data fd;
|
||||||
|
fd.file = SndfileHandle(filename);
|
||||||
|
|
||||||
|
if (fd.file.error())
|
||||||
|
{
|
||||||
|
hack::error()("Cannot open audio file: ", filename);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sample_rate = fd.file.samplerate();
|
||||||
|
int channels = fd.file.channels();
|
||||||
|
|
||||||
|
// подготовка буфера
|
||||||
|
fd.buffer.resize(FRAMES_PER_BUFFER * channels);
|
||||||
|
play_impl(fd, file_callback, sample_rate, channels);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
145
src/hack/audio/record.hpp
Normal file
145
src/hack/audio/record.hpp
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <atomic>
|
||||||
|
#include <csignal>
|
||||||
|
#include <sndfile.h>
|
||||||
|
#include <portaudio.h>
|
||||||
|
#include <chrono>
|
||||||
|
#include "hack/audio/manager.hpp"
|
||||||
|
#include "hack/logger/logger.hpp"
|
||||||
|
|
||||||
|
namespace hack::audio
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
struct record_data
|
||||||
|
{
|
||||||
|
std::vector<double>* data;
|
||||||
|
size_t max_samples;
|
||||||
|
bool complete = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::atomic<bool> stop_recording { false };
|
||||||
|
|
||||||
|
// Колбэк для записи.
|
||||||
|
static int callback(const void* input_buffer, void*, unsigned long frames, const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags, void* user_data)
|
||||||
|
{
|
||||||
|
auto* rd = static_cast<record_data*>(user_data);
|
||||||
|
const auto* in = static_cast<const float*>(input_buffer);
|
||||||
|
|
||||||
|
if (rd->complete) return paComplete;
|
||||||
|
|
||||||
|
// добавляем samples в вектор
|
||||||
|
for (unsigned long i = 0; i < frames; ++i)
|
||||||
|
{
|
||||||
|
if (rd->data->size() < rd->max_samples)
|
||||||
|
rd->data->push_back(static_cast<double>(in[i]));
|
||||||
|
else
|
||||||
|
return paComplete;
|
||||||
|
}
|
||||||
|
return paContinue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработчик сигнала для остановки записи.
|
||||||
|
void signal_handler(int signal)
|
||||||
|
{
|
||||||
|
if (signal == SIGINT)
|
||||||
|
{
|
||||||
|
stop_recording = true;
|
||||||
|
hack::log()("Stopping recording...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void record(std::vector<double>& data, int sample_rate, int channels, double duration_seconds)
|
||||||
|
{
|
||||||
|
data.clear();
|
||||||
|
stop_recording = false;
|
||||||
|
|
||||||
|
// устанавливаем обработчик сигнала
|
||||||
|
// нужен для обработки сигнала Ctrl+C (прерывание с клавиатуры)
|
||||||
|
std::signal(SIGINT, signal_handler);
|
||||||
|
|
||||||
|
if (!pa_manager::instance().initialize())
|
||||||
|
{
|
||||||
|
hack::error()("Failed to initialize PortAudio");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// получаем информацию о default input device
|
||||||
|
PaDeviceIndex input_device = Pa_GetDefaultInputDevice();
|
||||||
|
if (input_device == paNoDevice)
|
||||||
|
{
|
||||||
|
hack::error()("No input device found!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PaDeviceInfo* device_info = Pa_GetDeviceInfo(input_device);
|
||||||
|
hack::log()("Using input device: ", device_info->name);
|
||||||
|
hack::log()("Sample rate: ", sample_rate);
|
||||||
|
hack::log()("Channels: ", channels);
|
||||||
|
hack::log()("Duration: ", duration_seconds, " sec.");
|
||||||
|
|
||||||
|
size_t max_samples = static_cast<size_t>(sample_rate * duration_seconds);
|
||||||
|
data.reserve(max_samples);
|
||||||
|
|
||||||
|
record_data rd;
|
||||||
|
rd.data = &data;
|
||||||
|
rd.max_samples = max_samples;
|
||||||
|
rd.complete = false;
|
||||||
|
|
||||||
|
PaStreamParameters input_parameters;
|
||||||
|
input_parameters.device = input_device;
|
||||||
|
input_parameters.channelCount = channels;
|
||||||
|
input_parameters.sampleFormat = paFloat32;
|
||||||
|
input_parameters.suggestedLatency = device_info->defaultLowInputLatency;
|
||||||
|
input_parameters.hostApiSpecificStreamInfo = nullptr;
|
||||||
|
|
||||||
|
PaStream* stream;
|
||||||
|
PaError err = Pa_OpenStream(&stream,
|
||||||
|
&input_parameters,
|
||||||
|
nullptr, // No output
|
||||||
|
sample_rate,
|
||||||
|
512,
|
||||||
|
paClipOff,
|
||||||
|
callback,
|
||||||
|
&rd);
|
||||||
|
|
||||||
|
if (err != paNoError)
|
||||||
|
{
|
||||||
|
hack::error()("PortAudio error: ", Pa_GetErrorText(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
err = Pa_StartStream(stream);
|
||||||
|
if (err != paNoError)
|
||||||
|
{
|
||||||
|
Pa_CloseStream(stream);
|
||||||
|
hack::error()("PortAudio error: ", Pa_GetErrorText(err));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hack::warn()("Recording... Press Ctrl+C to stop early");
|
||||||
|
|
||||||
|
// основной цикл записи
|
||||||
|
auto start_time = std::chrono::steady_clock::now();
|
||||||
|
while (Pa_IsStreamActive(stream) && !rd.complete && !stop_recording)
|
||||||
|
{
|
||||||
|
auto current_time = std::chrono::steady_clock::now();
|
||||||
|
auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(current_time - start_time).count();
|
||||||
|
|
||||||
|
if (elapsed >= duration_seconds) break;
|
||||||
|
Pa_Sleep(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// завершение записи
|
||||||
|
err = Pa_StopStream(stream);
|
||||||
|
if (err != paNoError)
|
||||||
|
hack::error()("PortAudio error stopping stream: ", Pa_GetErrorText(err));
|
||||||
|
|
||||||
|
Pa_CloseStream(stream);
|
||||||
|
|
||||||
|
hack::log()("Recording finished. Captured ", data.size(), " samples (", static_cast<double>(data.size()) / sample_rate, " seconds)");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,9 +8,18 @@
|
|||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <forward_list>
|
#include <forward_list>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <string>
|
||||||
|
#include <deque>
|
||||||
|
#include <array>
|
||||||
|
#include <stack>
|
||||||
|
#include <queue>
|
||||||
|
#include <tuple>
|
||||||
|
#include <utility>
|
||||||
#include <concepts>
|
#include <concepts>
|
||||||
|
|
||||||
|
// HERE
|
||||||
|
// Это старая версия. Перед удалением нужнопроверить ее работу по сравнению с работой новой версии
|
||||||
namespace hack::concepts
|
namespace hack::concepts
|
||||||
{
|
{
|
||||||
template<typename T>
|
template<typename T>
|
||||||
@@ -40,13 +49,261 @@ namespace hack::concepts
|
|||||||
|
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
concept not_defined = !std::enable_if_t<!(std::integral<T> ||
|
concept not_defined = !std::enable_if_t<!(std::integral<T> || is_sequence_container<T> || is_map<T> || is_tuple<T> || is_set<T> || is_unordered_set<T> ||
|
||||||
is_sequence_container<T> ||
|
is_forward_list<T> || std::is_array<T>() || is_string<T>), bool>() == true;
|
||||||
is_map<T> ||
|
}
|
||||||
is_tuple<T> ||
|
|
||||||
is_set<T> ||
|
namespace hack::concepts::modern
|
||||||
is_unordered_set<T> ||
|
{
|
||||||
is_forward_list<T> ||
|
// Базовые концепты для обнаружения характеристик типов.
|
||||||
std::is_array<T>() ||
|
// Эти концепты являются строительными блоками для более сложных проверок.
|
||||||
is_string<T>), bool>() == true;
|
|
||||||
|
// @brief Проверяет, имеет ли тип вложенный тип value_type
|
||||||
|
// @details Используется для обнаружения контейнеров, которые хранят элементы
|
||||||
|
// Подходит для vector, list, set и других контейнеров STL
|
||||||
|
template<typename T>
|
||||||
|
concept has_value_type = requires { typename T::value_type; };
|
||||||
|
|
||||||
|
// @brief Проверяет, имеет ли тип вложенный тип key_type
|
||||||
|
// @details Характерно для ассоциативных контейнеров (map, set)
|
||||||
|
// Помогает отличать контейнеры по ключам от контейнеров по значениям
|
||||||
|
template<typename T>
|
||||||
|
concept has_key_type = requires { typename T::key_type; };
|
||||||
|
|
||||||
|
// @brief Проверяет, имеет ли тип вложенный тип mapped_type
|
||||||
|
// @details Специфично для map-подобных контейнеров, которые хранят пары ключ-значение
|
||||||
|
// Отличает map от set (у set нет mapped_type)
|
||||||
|
template<typename T>
|
||||||
|
concept has_mapped_type = requires { typename T::mapped_type; };
|
||||||
|
|
||||||
|
// @brief Проверяет, имеет ли тип методы begin() и end()
|
||||||
|
// @details Основной концепт для обнаружения итерируемых типов
|
||||||
|
// Работает с любым типом, который можно использовать в range-based for loops
|
||||||
|
template<typename T>
|
||||||
|
concept has_iterator = requires(T t) { t.begin(); t.end(); };
|
||||||
|
|
||||||
|
// @brief Проверяет, имеет ли тип метод size()
|
||||||
|
// @details Обнаруживает контейнеры, которые знают свой размер
|
||||||
|
// Важно для алгоритмов, требующих знания размера контейнера
|
||||||
|
template<typename T>
|
||||||
|
concept has_size = requires(T t) { t.size(); };
|
||||||
|
|
||||||
|
// @brief Проверяет, имеет ли тип вложенный тип key_compare
|
||||||
|
// @details Специфично для упорядоченных ассоциативных контейнеров (map, set)
|
||||||
|
// Помогает определить, используется ли компаратор для упорядочивания
|
||||||
|
template<typename T>
|
||||||
|
concept has_key_compare = requires { typename T::key_compare; };
|
||||||
|
|
||||||
|
// @brief Проверяет, имеет ли тип вложенный тип allocator_type
|
||||||
|
// @details Обнаруживает контейнеры, которые используют аллокаторы
|
||||||
|
// Полезно для кастомного управления памятью и продвинутых оптимизаций
|
||||||
|
template<typename T>
|
||||||
|
concept has_allocator_type = requires { typename T::allocator_type; };
|
||||||
|
|
||||||
|
// Строковые типы
|
||||||
|
// @brief Проверяет, является ли тип строковым
|
||||||
|
// @details Универсальная проверка для всех видов строк:
|
||||||
|
// - std::string и его варианты (wstring, u16string, u32string)
|
||||||
|
// - Строковые литералы (char*, const char*)
|
||||||
|
// - std::string_view
|
||||||
|
// Конвертация в string_view охватывает большинство строкоподобных типов
|
||||||
|
template<typename T>
|
||||||
|
concept is_string = std::convertible_to<T, std::string_view> || std::same_as<T, std::string> || std::same_as<T, std::wstring> || std::same_as<T, std::u16string> || std::same_as<T, std::u32string>;
|
||||||
|
|
||||||
|
// Последовательные контейнеры
|
||||||
|
// @brief Проверяет, является ли тип последовательным контейнером STL
|
||||||
|
// @details Обнаруживает стандартные последовательные контейнеры:
|
||||||
|
// - std::vector - динамический массив
|
||||||
|
// - std::list - двусвязный список
|
||||||
|
// - std::deque - двусторонняя очередь
|
||||||
|
// - std::forward_list - односвязный список
|
||||||
|
// Проверяет наличие итераторов и value_type для универсальности
|
||||||
|
template<typename T>
|
||||||
|
concept is_sequence_container = has_iterator<T> &&
|
||||||
|
has_value_type<T> &&
|
||||||
|
(std::same_as<T, std::vector<typename T::value_type, typename T::allocator_type>> ||
|
||||||
|
std::same_as<T, std::list<typename T::value_type, typename T::allocator_type>> ||
|
||||||
|
std::same_as<T, std::deque<typename T::value_type, typename T::allocator_type>> ||
|
||||||
|
std::same_as<T, std::forward_list<typename T::value_type, typename T::allocator_type>>);
|
||||||
|
|
||||||
|
// Контейнеры с произвольным доступом
|
||||||
|
// @brief Проверяет, поддерживает ли контейнер произвольный доступ
|
||||||
|
// @details Обнаруживает контейнеры с оператором [] для быстрого доступа:
|
||||||
|
// - std::vector - O(1) доступ по индексу
|
||||||
|
// - std::deque - O(1) доступ по индексу
|
||||||
|
// - std::array - фиксированный массив с быстрым доступом
|
||||||
|
// Важно для алгоритмов, требующих частого доступа к элементам по индексу
|
||||||
|
template<typename T>
|
||||||
|
concept is_random_access_container = is_sequence_container<T> && requires(T t, std::size_t idx) { t[idx]; };
|
||||||
|
|
||||||
|
// Адаптеры контейнеров
|
||||||
|
// @brief Проверяет, является ли тип адаптером контейнера
|
||||||
|
// @details Обнаруживает типы-обертки над другими контейнерами:
|
||||||
|
// - std::stack - LIFO стек
|
||||||
|
// - std::queue - FIFO очередь
|
||||||
|
// - std::priority_queue - очередь с приоритетом
|
||||||
|
// Эти типы предоставляют ограниченный интерфейс поверх базовых контейнеров
|
||||||
|
template<typename T>
|
||||||
|
concept is_container_adapter = std::same_as<T, std::stack<typename T::value_type, typename T::container_type>> ||
|
||||||
|
std::same_as<T, std::queue<typename T::value_type, typename T::container_type>> ||
|
||||||
|
std::same_as<T, std::priority_queue<typename T::value_type, typename T::container_type>>;
|
||||||
|
|
||||||
|
// Ассоциативные контейнеры (универсальная проверка)
|
||||||
|
// @brief Проверяет, является ли тип упорядоченным ассоциативным контейнером
|
||||||
|
// @details Обнаруживает контейнеры, хранящие элементы в отсортированном порядке:
|
||||||
|
// - std::map - словарь с уникальными ключами
|
||||||
|
// - std::multimap - словарь с возможностью дубликатов ключей
|
||||||
|
// - std::set - множество уникальных элементов
|
||||||
|
// - std::multiset - множество с возможностью дубликатов
|
||||||
|
// Использует key_compare для упорядочивания (обычно std::less)
|
||||||
|
template<typename T>
|
||||||
|
concept is_associative_container = has_iterator<T> &&
|
||||||
|
has_key_type<T> &&
|
||||||
|
(has_mapped_type<T> || !has_mapped_type<T>) && // Для map и set
|
||||||
|
(std::same_as<T, std::map<typename T::key_type, typename T::mapped_type, typename T::key_compare, typename T::allocator_type>> ||
|
||||||
|
std::same_as<T, std::multimap<typename T::key_type, typename T::mapped_type, typename T::key_compare, typename T::allocator_type>> ||
|
||||||
|
std::same_as<T, std::set<typename T::key_type, typename T::key_compare, typename T::allocator_type>> ||
|
||||||
|
std::same_as<T, std::multiset<typename T::key_type, typename T::key_compare, typename T::allocator_type>>);
|
||||||
|
|
||||||
|
// Неупорядоченные ассоциативные контейнеры
|
||||||
|
// @brief Проверяет, является ли тип неупорядоченным ассоциативным контейнером
|
||||||
|
// @details Обнаруживает контейнеры, использующие хеширование:
|
||||||
|
// - std::unordered_map - хеш-таблица с уникальными ключами
|
||||||
|
// - std::unordered_multimap - хеш-таблица с дубликатами ключей
|
||||||
|
// - std::unordered_set - хеш-множество уникальных элементов
|
||||||
|
// - std::unordered_multiset - хеш-множество с дубликатами
|
||||||
|
// Используют хеш-функции и сравнение на равенство вместо упорядочивания
|
||||||
|
template<typename T>
|
||||||
|
concept is_unordered_associative_container = has_iterator<T> && has_key_type<T> && (has_mapped_type<T> || !has_mapped_type<T>) &&
|
||||||
|
(std::same_as<T, std::unordered_map<typename T::key_type, typename T::mapped_type, typename T::hasher, typename T::key_equal, typename T::allocator_type>> ||
|
||||||
|
std::same_as<T, std::unordered_multimap<typename T::key_type, typename T::mapped_type, typename T::hasher, typename T::key_equal, typename T::allocator_type>> ||
|
||||||
|
std::same_as<T, std::unordered_set<typename T::key_type, typename T::hasher, typename T::key_equal, typename T::allocator_type>> ||
|
||||||
|
std::same_as<T, std::unordered_multiset<typename T::key_type, typename T::hasher, typename T::key_equal, typename T::allocator_type>>);
|
||||||
|
|
||||||
|
// Кортежи и пары
|
||||||
|
// @brief Проверяет, является ли тип кортежоподобным
|
||||||
|
// @details Обнаруживает типы, которые можно разбирать через tuple_size и tuple_element:
|
||||||
|
// - std::tuple - гетерогенный кортеж
|
||||||
|
// - std::pair - пара из двух элементов
|
||||||
|
// - std::array - также удовлетворяет этому концепту
|
||||||
|
// - Пользовательские кортежи с специализацией tuple_size
|
||||||
|
// Исключает строки и контейнеры для избежания ложных срабатываний
|
||||||
|
template<typename T>
|
||||||
|
concept is_tuple_like = requires(T t) {
|
||||||
|
std::tuple_size<T>::value;
|
||||||
|
requires std::tuple_size<T>::value >= 0;
|
||||||
|
} && !is_string<T> && !is_sequence_container<T>; // Исключаем конфликты
|
||||||
|
|
||||||
|
// Массивы
|
||||||
|
// @brief Проверяет, является ли тип встроенным массивом
|
||||||
|
// @details Обнаруживает C-style массивы: int[10], char[5], etc.
|
||||||
|
// Проверяет, что размер массива больше 0 (не incomplete array)
|
||||||
|
template<typename T>
|
||||||
|
concept is_fixed_array = std::is_array_v<T> && std::extent_v<T> > 0;
|
||||||
|
|
||||||
|
// @brief Проверяет, является ли тип std::array
|
||||||
|
// @details Обнаруживает std::array с конкретным размером
|
||||||
|
// Полезно для алгоритмов, которые требуют знания размера на этапе компиляции
|
||||||
|
template<typename T, std::size_t N>
|
||||||
|
concept is_std_array = std::same_as<T, std::array<typename T::value_type, N>>;
|
||||||
|
|
||||||
|
// Универсальные концепты для категоризации
|
||||||
|
// @brief Проверяет, является ли тип любым контейнером
|
||||||
|
// @details Всеобъемлющий концепт для обнаружения контейнеров любого типа:
|
||||||
|
// - Последовательные контейнеры
|
||||||
|
// - Ассоциативные контейнеры
|
||||||
|
// - Адаптеры контейнеров
|
||||||
|
// - Массивы (C-style и std::array)
|
||||||
|
// Основной концепт для обобщенных алгоритмов работы с контейнерами
|
||||||
|
template<typename T>
|
||||||
|
concept is_any_container = is_sequence_container<T> ||
|
||||||
|
is_associative_container<T> ||
|
||||||
|
is_unordered_associative_container<T> ||
|
||||||
|
is_container_adapter<T> ||
|
||||||
|
is_fixed_array<T> ||
|
||||||
|
is_std_array<T, std::tuple_size_v<T>>;
|
||||||
|
|
||||||
|
// @brief Проверяет, является ли тип итерируемым
|
||||||
|
// @details Обнаруживает любые типы, по которым можно итерироваться:
|
||||||
|
// - Контейнеры STL с begin()/end()
|
||||||
|
// - Массивы (работают с std::begin/std::end)
|
||||||
|
// - Кортежи (хотя итерирование по ним особое)
|
||||||
|
// Самый общий концепт для range-based for и алгоритмов
|
||||||
|
template<typename T>
|
||||||
|
concept is_iterable = has_iterator<T> || is_fixed_array<T> || is_tuple_like<T>;
|
||||||
|
|
||||||
|
// @brief Проверяет, имеет ли тип размер
|
||||||
|
// @details Обнаруживает типы, у которых можно узнать размер:
|
||||||
|
// - Контейнеры с методом size()
|
||||||
|
// - Массивы с известным размером
|
||||||
|
// - Кортежи с известным количеством элементов
|
||||||
|
// Важно для алгоритмов, требующих предварительного знания размера
|
||||||
|
template<typename T>
|
||||||
|
concept is_sized = has_size<T> || is_fixed_array<T> || is_tuple_like<T>;
|
||||||
|
|
||||||
|
// Концепт для "неподдерживаемых" типов
|
||||||
|
// @brief Проверяет, является ли тип неподдерживаемым
|
||||||
|
// @details Обнаруживает типы, которые не входят в известные категории:
|
||||||
|
// - Пользовательские типы без ожидаемого интерфейса
|
||||||
|
// - Специфичные типы из сторонних библиотек
|
||||||
|
// - Типы, для которых нет специализированной обработки
|
||||||
|
// Используется для static_assert и генерации понятных ошибок
|
||||||
|
template<typename T>
|
||||||
|
concept not_supported = !(std::integral<T> ||
|
||||||
|
std::floating_point<T> ||
|
||||||
|
is_string<T> ||
|
||||||
|
is_any_container<T> ||
|
||||||
|
is_tuple_like<T> ||
|
||||||
|
std::is_pointer_v<T>);
|
||||||
|
|
||||||
|
// Вспомогательные концепты для метапрограммирования
|
||||||
|
// @brief Проверяет, имеет ли тип семантику ключ-значение
|
||||||
|
// @details Обнаруживает контейнеры, которые поддерживают доступ по ключу:
|
||||||
|
// - map и unordered_map (operator[])
|
||||||
|
// - set и unordered_set (хотя у set нет разделения на key/value)
|
||||||
|
// Полезно для алгоритмов работы со словарями и ассоциативными массивами
|
||||||
|
template<typename T>
|
||||||
|
concept has_key_value_semantics = is_associative_container<T> ||
|
||||||
|
is_unordered_associative_container<T> ||
|
||||||
|
requires(T t, typename T::key_type key) { t[key]; };
|
||||||
|
|
||||||
|
// @brief Проверяет, является ли тип контейнером с непрерывной памятью
|
||||||
|
// @details Обнаруживает контейнеры, элементы которых хранятся в непрерывной памяти:
|
||||||
|
// - std::vector - динамический массив
|
||||||
|
// - C-style массивы
|
||||||
|
// - std::array - статический массив
|
||||||
|
// Критически важно для низкоуровневых операций и взаимодействия с C API
|
||||||
|
template<typename T>
|
||||||
|
concept is_contiguous_container = std::same_as<T, std::vector<typename T::value_type, typename T::allocator_type>> ||
|
||||||
|
is_fixed_array<T> ||
|
||||||
|
is_std_array<T, std::tuple_size_v<T>>;
|
||||||
|
|
||||||
|
// Концепты для алгоритмов
|
||||||
|
// @brief Проверяет, поддерживает ли контейнер emplace-операцию
|
||||||
|
// @details Обнаруживает контейнеры, которые могут создавать элементы на месте:
|
||||||
|
// - vector::emplace_back(), map::emplace(), etc.
|
||||||
|
// Позволяет избежать лишних копирований и перемещений
|
||||||
|
template<typename Container, typename Value>
|
||||||
|
concept can_emplace = requires(Container c, Value&& v) { c.emplace(std::forward<Value>(v)); };
|
||||||
|
|
||||||
|
// @brief Проверяет, поддерживает ли контейнер добавление в начало
|
||||||
|
// @details Обнаруживает контейнеры с push_front():
|
||||||
|
// - deque, list, forward_list
|
||||||
|
// Полезно для алгоритмов, работающих с очередями и стеками
|
||||||
|
template<typename Container, typename Value>
|
||||||
|
concept can_push_front = requires(Container c, Value&& v) { c.push_front(std::forward<Value>(v)); };
|
||||||
|
|
||||||
|
// @brief Проверяет, поддерживает ли контейнер добавление в конец
|
||||||
|
// @details Обнаруживает контейнеры с push_back():
|
||||||
|
// - vector, deque, list
|
||||||
|
// Важно для алгоритмов, которые строят контейнеры последовательно
|
||||||
|
template<typename Container, typename Value>
|
||||||
|
concept can_push_back = requires(Container c, Value&& v) { c.push_back(std::forward<Value>(v)); };
|
||||||
|
|
||||||
|
// @brief Проверяет, поддерживает ли контейнер поиск по ключу
|
||||||
|
// @details Обнаруживает контейнеры с методом find():
|
||||||
|
// - map, set, unordered_map, unordered_set
|
||||||
|
// Ключевой концепт для алгоритмов поиска и проверки существования элементов
|
||||||
|
template<typename Container, typename Key>
|
||||||
|
concept can_find = requires(Container c, Key&& key) { c.find(std::forward<Key>(key)); };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user