Zachary Hall
ae74999276
Some checks failed
Build / build-gentoo (push) Failing after 1m25s
Build / download-system-deps (push) Successful in 3m44s
Build / get-source-code (push) Successful in 6m52s
Build / build-appimage (push) Successful in 3m29s
Build / build-android (push) Failing after 2m52s
Build / build-windows (push) Failing after 6m42s
577 lines
15 KiB
C++
577 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 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(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);
|
|
};
|