looper/util.hpp
Zachary Hall b886e6a585
Some checks failed
Build / build-gentoo (push) Failing after 2m4s
Build / download-system-deps (push) Successful in 4m1s
Build / get-source-code (push) Successful in 12m47s
Build / build-appimage (push) Successful in 4m11s
Build / build-android (push) Failing after 3m1s
Build / build-windows (push) Failing after 7m25s
Add Fluidsynth backend and stop using SDL Mixer X for that.
2024-12-11 12:56:17 -08:00

584 lines
15 KiB
C++

#pragma once
#include <exception>
#include <string>
#include <string.h>
#include <stddef.h>
#include <stdint.h>
#include <cmath>
#include <vector>
#include <stdarg.h>
#include <optional>
#include <mutex>
#include <cmath>
#include <atomic>
#include <stack>
#include <filesystem>
#include <queue>
#include <cassert>
#include <SDL.h>
#include "log.hpp"
#include <ipc/common.pb.h>
namespace fs = std::filesystem;
std::string PadZeros(std::string input, size_t required_length);
uint8_t TimeToComponentCount(double time_code);
std::string TimeToString(double time_code, uint8_t min_components = 1);
std::string get_prefs_path();
std::string hexdump(void *ptr, size_t len);
inline std::string hexdump(std::string data) {
return hexdump(data.data(), data.length());
}
inline const char *vcformat(const char *format, va_list args) {
va_list args_copy;
va_copy(args_copy, args);
char *buf;
size_t buflen = 0;
buflen = vsnprintf(NULL, 0, format, args_copy) + 1;
va_end(args_copy);
buf = (char*)malloc(buflen);
if (buf == NULL) {
return NULL;
}
memset(buf, 0, buflen);
buflen = vsnprintf(buf, buflen, format, args) + 1;
return buf;
}
inline const char *cformat(const char *format, ...) {
va_list args;
va_start(args, format);
const char *output = vcformat(format, args);
va_end(args);
return output;
}
inline std::string to_string_with_decimals(double value, unsigned decimals) {
std::string num_text;
if (value == 0) {
num_text = "0";
} else {
size_t buflen = snprintf(NULL, 0, "%f", value);
buflen++;
char *buf = (char*)malloc(buflen);
memset(buf, 0, buflen);
snprintf(buf, buflen - 1, "%f", value);
num_text = std::string(buf);
free(buf);
}
int found = num_text.find(".");
if (found == -1) {
found = num_text.size();
num_text.append(".0");
}
if (num_text[0] == '.') {
num_text = "0" + num_text;
found++;
}
if (decimals == 0) {
num_text = num_text.substr(0, found);
return num_text;
}
int chars_after_decimal = num_text.size() - found - 1;
if (chars_after_decimal > decimals) {
num_text = num_text.substr(0, found + decimals + 1);
} else {
for (int i = chars_after_decimal; i < decimals; i++) {
num_text.push_back('0');
}
}
return num_text;
}
inline size_t combine_hashes(std::initializer_list<size_t> hashes) {
std::string values = "^";
values += hashes.size();
values += "@";
for (auto value : values) {
values += value;
values += ";";
}
return std::hash<std::string>()(values);
}
inline bool is_zeroes(void *ptr, size_t len) {
uint8_t *ptr8 = (uint8_t*)ptr;
for (size_t i = 0; i < len; i++) {
if (ptr8[i] != 0) {
return false;
}
}
return true;
}
inline void replace_all(std::string &input, std::string replace, std::string value) {
size_t pos = 0;
while (true) {
pos = input.find(replace, pos);
if (pos >= input.length()) break;
input.replace(pos, pos + replace.length(), value);
pos += value.length() - replace.length();
}
}
fs::path get_base_dir();
int launch(std::vector<std::string> args);
int launch_self(std::string process_type, std::vector<std::string> extra_args);
#ifdef MIN
#undef MIN
#endif
#define MIN(x, y) ((x < y) ? x : y)
#ifdef MAX
#undef MAX
#endif
#define MAX(x, y) ((x > y) ? x : y)
#ifdef ABS
#undef ABS
#endif
#define ABS(x) ((x < 0) ? -x : x)
template<class T>
class Lock;
template<class T>
class LockAccessor {
friend class Lock<T>;
Lock<T> *lock;
bool locked = true;
LockAccessor(Lock<T> *lock) {
this->lock = lock;
}
public:
/// @brief Gets the value of the lock.
/// @returns The value of the lock
T get() {
if (has_value()) {
return *lock->value;
} else {
throw std::exception();
}
}
/// @brief Sets the value of the lock, deinitializing the previous value in the process.
/// @param value The new value of the lock
/// @param owned Set to true to have the lock "own" the value. If true, the lock will deinitialize the value.
void set(T value, bool owned = false) {
lock->clear_value();
lock->value = new T(value);
lock->owned = owned;
}
T &operator *() {
if (has_value()) {
return *lock->value;
} else {
throw std::exception();
}
}
T *operator->() {
if (has_value()) {
return lock->value;
} else {
throw std::exception();
}
}
void clear() {
lock->clear_value();
}
bool has_value() {
return lock->has_value();
}
void unlock() {
if (locked) {
lock->mutex.unlock();
locked = false;
}
}
~LockAccessor() {
unlock();
}
};
template<class T>
class LockAccessor<T*> {
friend class Lock<T*>;
Lock<T*> *lock;
std::atomic_bool locked = true;
LockAccessor(Lock<T*> *lock) {
this->lock = lock;
}
public:
T &operator *() {
if (has_value()) {
return **lock->value;
} else {
throw std::exception();
}
}
/// @brief Gets the value of the lock.
/// @returns The value of the lock
T *get() {
if (has_value()) {
return *lock->value;
} else {
throw std::exception();
}
}
/// @brief Sets the value of the lock, deinitializing the previous value in the process.
/// @param value The new value of the lock
/// @param owned Set to true to have the lock "own" the value. If true, the lock will deinitialize the value.
void set(T *value, bool owned = false) {
lock->clear_value();
lock->value = new T*(value);
lock->owned = owned;
}
T *operator->() {
if (has_value()) {
return *lock->value;
} else{
throw std::exception();
}
}
void clear() {
lock->clear_value();
}
void unlock() {
if (locked.exchange(false)) {
lock->mutex->unlock();
}
}
bool has_value() {
return lock->has_value();
}
~LockAccessor() {
unlock();
}
};
template<class T>
class Lock {
friend class LockAccessor<T>;
T *value;
std::recursive_mutex *mutex;
bool owned = false;
void clear_value() {
if (owned && this->value != nullptr) {
if constexpr (std::is_pointer_v<T>) {
delete *this->value;
}
delete this->value;
}
this->value = nullptr;
}
public:
bool has_value() {
return this->value != nullptr;
}
T &get_unsafe() {
if (has_value()) {
return *value;
} else {
throw std::exception();
}
}
LockAccessor<T> lock() {
mutex->lock();
return LockAccessor(this);
}
std::optional<LockAccessor<T>> try_lock(){
if (mutex->try_lock()) {
return LockAccessor(this);
} else {
return {};
}
}
/// @brief Constructs a lock on a value. Is owner is true, the value will be owned by the lock.
/// @param value The value to use with the lock
/// @param owner Set to true to have the lock delete the value on deinitialization
Lock(T value, bool owned = false)
: owned(owned)
{
this->value = new T(value);
mutex = new std::recursive_mutex();
}
Lock()
: value(nullptr)
, owned(false)
{
mutex = new std::recursive_mutex();
}
Lock<T> &operator=(Lock<T> lock) = delete;
~Lock() {
mutex->lock();
clear_value();
mutex->unlock();
delete this->mutex;
}
};
class DynPtr {
public:
void *ptr;
private:
size_t ptr_len;
size_t adjust_size(size_t len) {
static const size_t chunk = 1024;
size_t value = len;
value /= chunk;
if (len % chunk != 0) {
value += 1;
}
value *= chunk;
return value;
}
public:
DynPtr() {
this->ptr = NULL;
this->ptr_len = 0;
}
void resize(size_t new_len) {
new_len = adjust_size(new_len);
if (this->ptr == NULL) {
this->ptr = malloc(new_len);
this->ptr_len = new_len;
} else if (this->ptr_len != new_len) {
this->ptr = realloc(this->ptr, new_len);
this->ptr_len = new_len;
}
assert(this->ptr != NULL);
}
/// @brief Creates a DynPtr object and resizes it.
/// @param len The minimum length of the pointer.
DynPtr(size_t len) : DynPtr() {
resize(len);
}
DynPtr(std::string input) : DynPtr(input.length()) {
memcpy(this->ptr, input.data(), input.length());
}
/// @brief Gets the pointer, but does not reallocate it.
/// @param T The type of pointer to return. May be void to return void*.
/// @returns The pointer, owned by the DynPtr object.
template<class T>
T *get() {
assert(ptr != NULL);
if constexpr (std::is_same_v<T, void>) {
return ptr;
} else {
return (T*)ptr;
}
}
/// @brief Reallocates the pointer, and returns it.
/// @param len The amount of bytes for the pointer
/// @param T The type of pointer to return. May be void to return void*
/// @returns The reallocated pointer, owned by the DynPtr object.
template<class T>
T *get_byte_sized(size_t len) {
resize(len);
return get<T>();
}
/// @brief Reallocates the pointer, and returns it.
/// @param len The amount of items the pointer should make room for. For void pointers, this is actually the amount of bytes.
/// @param T The type of pointer to return, and to use in the sizeof call when not void.
/// @returns The reallocated pointer, owned by the DynPtr object.
template<class T>
T *get_item_sized(size_t len) {
if constexpr (std::is_same_v<T, void>) {
return get_byte_sized<T>(len);
} else {
return get_byte_sized<T>(len * sizeof(T));
}
}
};
template <typename T>
class Fifo {
private:
std::vector<T> buffer;
size_t head = 0;
size_t tail = 0;
size_t count = 0;
public:
Fifo(size_t initial_capacity = 16) : buffer(initial_capacity) {}
void resize(size_t new_capacity) {
std::vector<T> new_buffer(new_capacity);
size_t new_count = std::min(count, new_capacity);
for (size_t i = 0; i < new_count; ++i) {
new_buffer[i] = buffer[(head + i) % buffer.size()];
}
buffer = std::move(new_buffer);
head = 0;
tail = new_count;
count = new_count;
}
size_t size() const {
return count;
}
size_t capacity() const {
return buffer.size();
}
void push(const T& value) {
if (count == buffer.size()) {
resize(buffer.size() * 2);
}
buffer[tail] = value;
tail = (tail + 1) % buffer.size();
++count;
}
void push(const T* values, size_t length) {
if (count + length > buffer.size()) {
resize(std::max(buffer.size() * 2, count + length));
}
for (size_t i = 0; i < length; ++i) {
buffer[tail] = values[i];
tail = (tail + 1) % buffer.size();
++count;
}
}
T pop() {
if (count == 0) {
throw std::out_of_range("FIFO is empty");
}
T value = buffer[head];
head = (head + 1) % buffer.size();
--count;
return value;
}
size_t pop(T* output, size_t max_length) {
size_t popped = std::min(count, max_length);
for (size_t i = 0; i < popped; ++i) {
output[i] = buffer[head];
head = (head + 1) % buffer.size();
}
count -= popped;
return popped;
}
T* reserve(size_t length) {
if (count + length > buffer.size()) {
resize(std::max(buffer.size() * 2, count + length));
}
T* start = &buffer[tail];
tail = (tail + length) % buffer.size();
count += length;
return start;
}
bool empty() const {
return count == 0;
}
void clear() {
count = 0;
head = 0;
tail = 0;
}
void shrink() {
size_t offs = size() - capacity();
if (offs != 0) {
size_t old_head = head;
size_t old_tail = tail;
size_t new_head = head - offs;
size_t new_tail = tail - offs;
size_t start = std::min(head, tail);
size_t end = std::max(head, tail);
size_t len = end - start;
for (size_t i = 0; i < len; i++) {
size_t j = i + start;
buffer[i] = buffer[j];
}
head = new_head;
tail = new_tail;
}
buffer.resize(size());
}
};
void blocking_write(int fd, const void *buf, const size_t len);
void blocking_read(int fd, void *buf, const size_t len);
int NotSDL_ConvertAudioSamples(const SDL_AudioSpec *src_spec, const Uint8 *src_data, int src_len,
const SDL_AudioSpec *dst_spec, Uint8 **dst_data, int *dst_len);
template<class T>
inline T *resolve_any(google::protobuf::Any value) {
T *output = new T();
value.UnpackTo(output);
return output;
}
template<class T>
inline T resolve_value(google::protobuf::Any value) {
throw std::exception();
}
template<>
inline bool resolve_value<bool>(google::protobuf::Any value) {
BooleanProperty *property = resolve_any<BooleanProperty>(value);
bool output = property->value();
delete property;
return output;
}
template<>
inline std::string resolve_value<std::string>(google::protobuf::Any value) {
StringProperty *property = resolve_any<StringProperty>(value);
std::string output = property->value();
delete property;
return output;
}
template<>
inline int resolve_value<int>(google::protobuf::Any value) {
IntProperty *property = resolve_any<IntProperty>(value);
int output = property->value();
delete property;
return output;
}
template<>
inline DynPtr resolve_value<DynPtr>(google::protobuf::Any value) {
BytesProperty *property = resolve_any<BytesProperty>(value);
std::string output = property->value();
delete property;
return DynPtr(output);
}
template<>
inline double resolve_value<double>(google::protobuf::Any value) {
DoubleProperty *property = resolve_any<DoubleProperty>(value);
double output = property->value();
delete property;
return output;
}
template<class T>
inline google::protobuf::Any value_to_any(T value) {
google::protobuf::Any output;
output.PackFrom(value);
return output;
}
template<>
inline google::protobuf::Any value_to_any<double>(double value) {
DoubleProperty output;
output.set_value(value);
return value_to_any<DoubleProperty>(output);
}
template<>
inline google::protobuf::Any value_to_any<bool>(bool value) {
BooleanProperty output;
output.set_value(value);
return value_to_any<BooleanProperty>(output);
}
template<>
inline google::protobuf::Any value_to_any<std::string>(std::string value) {
StringProperty output;
output.set_value(value);
return value_to_any<StringProperty>(output);
}
PropertyHint make_hint(double min, double max);
Property make_property(PropertyType type, std::string name, PropertyId id, std::optional<PropertyHint> hint);
Property make_property(PropertyType type, std::string name, std::string path, std::optional<PropertyHint> hint = {});
Property make_property(PropertyType type, std::string name, PropertyId id = PropertyId::BackendSpecific, std::optional<std::string> path = {}, std::optional<PropertyHint> hint = {});
class LooperLogScaler {
double la;
double lb;
public:
double x0;
double x1;
void update_min_max(double min, double max);
LooperLogScaler(double min, double max);
double scale_log(double value);
double unscale_log(double value);
};