Add QT6 frontend
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
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
This commit is contained in:
parent
8fdf30878b
commit
ae74999276
28 changed files with 1404 additions and 44 deletions
2
.clangd
2
.clangd
|
@ -1,2 +0,0 @@
|
||||||
CompileFlags:
|
|
||||||
CompilationDatabase: "./build/compile_commands.json"
|
|
20
.vscode/c_cpp_properties.json
vendored
Normal file
20
.vscode/c_cpp_properties.json
vendored
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Linux",
|
||||||
|
"includePath": [
|
||||||
|
"${workspaceFolder}/**",
|
||||||
|
"/usr/include/qt6/QtCore",
|
||||||
|
"/usr/include/qt6/QtGui",
|
||||||
|
"/usr/include/qt6/QtWidgets"
|
||||||
|
],
|
||||||
|
"defines": [],
|
||||||
|
"compilerPath": "/usr/bin/gcc",
|
||||||
|
"cStandard": "c17",
|
||||||
|
"cppStandard": "c++17",
|
||||||
|
"intelliSenseMode": "linux-gcc-x64",
|
||||||
|
"configurationProvider": "ms-vscode.cmake-tools"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": 4
|
||||||
|
}
|
0
.zed/settings.json
Normal file
0
.zed/settings.json
Normal file
|
@ -102,8 +102,6 @@ if (DEFINED EMSCRIPTEN)
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${EXTRA_FLAGS}")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${EXTRA_FLAGS}")
|
||||||
set(EXTRA_LINKER_FLAGS "${EXTRA_LINKER_FLAGS} ${EXTRA_FLAGS}")
|
set(EXTRA_LINKER_FLAGS "${EXTRA_LINKER_FLAGS} ${EXTRA_FLAGS}")
|
||||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${EXTRA_LINKER_FLAGS}")
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${EXTRA_LINKER_FLAGS}")
|
||||||
else()
|
|
||||||
set(BUILD_STATIC OFF CACHE BOOL "")
|
|
||||||
endif()
|
endif()
|
||||||
option(BUILD_SDL "Enables built-in SDL" OFF)
|
option(BUILD_SDL "Enables built-in SDL" OFF)
|
||||||
option(BUILD_SDL_IMAGE "Enables built-in SDL_image" ${BUILD_SDL})
|
option(BUILD_SDL_IMAGE "Enables built-in SDL_image" ${BUILD_SDL})
|
||||||
|
@ -160,7 +158,11 @@ add_subdirectory(subprojects/vgmstream)
|
||||||
if (DEFINED EMSCRIPTEN)
|
if (DEFINED EMSCRIPTEN)
|
||||||
set(EXTRA_LIBS )
|
set(EXTRA_LIBS )
|
||||||
else()
|
else()
|
||||||
|
if (BUILD_STATIC)
|
||||||
|
set(EXTRA_LIBS libvgmstream)
|
||||||
|
else()
|
||||||
set(EXTRA_LIBS libvgmstream_shared)
|
set(EXTRA_LIBS libvgmstream_shared)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
if(SDL_MIXER_X_STATIC)
|
if(SDL_MIXER_X_STATIC)
|
||||||
set(SDL_MIXER_X_TARGET SDL2_mixer_ext_Static)
|
set(SDL_MIXER_X_TARGET SDL2_mixer_ext_Static)
|
||||||
|
@ -402,7 +404,7 @@ else()
|
||||||
if (TARGET SDL2-static)
|
if (TARGET SDL2-static)
|
||||||
set(SDL2_TARGET SDL2-static)
|
set(SDL2_TARGET SDL2-static)
|
||||||
endif()
|
endif()
|
||||||
target_link_libraries(liblooper PUBLIC ${SDL2_TARGET} ${SDL_MIXER_X_TARGET} ${SOUNDTOUCH_TARGET} libvgmstream libvgmstream_shared ${JSONCPP_TARGET})
|
target_link_libraries(liblooper PUBLIC ${SDL2_TARGET} ${SDL_MIXER_X_TARGET} ${SOUNDTOUCH_TARGET} libvgmstream ${JSONCPP_TARGET})
|
||||||
endif()
|
endif()
|
||||||
if(BUILD_PROTOBUF)
|
if(BUILD_PROTOBUF)
|
||||||
add_subdirectory(subprojects/protobuf)
|
add_subdirectory(subprojects/protobuf)
|
||||||
|
@ -483,6 +485,7 @@ endmacro()
|
||||||
set(ENABLED_UIS )
|
set(ENABLED_UIS )
|
||||||
set(ENABLED_PLAYBACK_BACKENDS )
|
set(ENABLED_PLAYBACK_BACKENDS )
|
||||||
ui_backend_subdir(NAME "IMGUI" READABLE_NAME "Dear ImGui" SUBDIR backends/ui/imgui)
|
ui_backend_subdir(NAME "IMGUI" READABLE_NAME "Dear ImGui" SUBDIR backends/ui/imgui)
|
||||||
|
ui_backend_subdir(NAME "QT" READABLE_NAME "Qt" SUBDIR backends/ui/qt)
|
||||||
if(CMAKE_SYSTEM_NAME STREQUAL "Haiku")
|
if(CMAKE_SYSTEM_NAME STREQUAL "Haiku")
|
||||||
ui_backend_subdir(NAME "HAIKU" READABLE_NAME "Haiku Native" SUBDIR backends/ui/haiku)
|
ui_backend_subdir(NAME "HAIKU" READABLE_NAME "Haiku Native" SUBDIR backends/ui/haiku)
|
||||||
endif()
|
endif()
|
||||||
|
@ -537,7 +540,7 @@ if(DEFINED EMSCRIPTEN)
|
||||||
copy_to_bindir(assets/ForkAwesome/css/fork-awesome.min.css fork-awesome.min.css)
|
copy_to_bindir(assets/ForkAwesome/css/fork-awesome.min.css fork-awesome.min.css)
|
||||||
copy_to_bindir(assets/ForkAwesome/css/fork-awesome.min.css.map fork-awesome.min.css.map)
|
copy_to_bindir(assets/ForkAwesome/css/fork-awesome.min.css.map fork-awesome.min.css.map)
|
||||||
endif()
|
endif()
|
||||||
target_link_libraries(${TARGET_NAME} PUBLIC liblooper ${UI_BACKENDS} ${PLAYBACK_BACKENDS})
|
target_link_libraries(${TARGET_NAME} PUBLIC liblooper ${UI_BACKENDS} ${PLAYBACK_BACKENDS} vorbis mpg123)
|
||||||
install(TARGETS ${TARGET_NAME} ${EXTRA_LIBS})
|
install(TARGETS ${TARGET_NAME} ${EXTRA_LIBS})
|
||||||
if (${BUILD_SDL2})
|
if (${BUILD_SDL2})
|
||||||
install(EXPORT SDL2-static SDL2main)
|
install(EXPORT SDL2-static SDL2main)
|
||||||
|
|
9
audio_output_backend.hpp
Normal file
9
audio_output_backend.hpp
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
#pragma once
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
#include "backend.hpp"
|
||||||
|
class AudioOutputBackend {
|
||||||
|
BACKEND_TYPE(AudioOutputBackend);
|
||||||
|
protected:
|
||||||
|
|
||||||
|
};
|
|
@ -279,23 +279,6 @@ void LooperWindow::MessageReceived(BMessage *msg) {
|
||||||
} break;
|
} break;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
LooperLogScaler::LooperLogScaler(double min, double max) {
|
|
||||||
update_min_max(min, max);
|
|
||||||
}
|
|
||||||
void LooperLogScaler::update_min_max(double min, double max) {
|
|
||||||
x0 = min;
|
|
||||||
x1 = (max - min) / (exp(1.0) - 1.0);
|
|
||||||
la = min;
|
|
||||||
lb = (max - min);
|
|
||||||
// x0 = scale_log(min);
|
|
||||||
// x1 = scale_log(max);
|
|
||||||
}
|
|
||||||
double LooperLogScaler::scale_log(double value) {
|
|
||||||
return (std::log(((value - x0) / x1) + 1.0) * lb) + la;
|
|
||||||
}
|
|
||||||
double LooperLogScaler::unscale_log(double value) {
|
|
||||||
return ((std::exp((value - la) / lb) - 1.0) * x1) + x0;
|
|
||||||
}
|
|
||||||
void LooperWindow::Pulse() {
|
void LooperWindow::Pulse() {
|
||||||
auto len = playback->GetLength();
|
auto len = playback->GetLength();
|
||||||
auto pos = playback->GetPosition();
|
auto pos = playback->GetPosition();
|
||||||
|
@ -324,7 +307,7 @@ void LooperWindow::Pulse() {
|
||||||
auto speed = playback->GetSpeed();
|
auto speed = playback->GetSpeed();
|
||||||
auto tempo = playback->GetTempo();
|
auto tempo = playback->GetTempo();
|
||||||
if (!volume_clicked) volume_slider->SetValue(volume);
|
if (!volume_clicked) volume_slider->SetValue(volume);
|
||||||
volume_slider->SetLabel(fmt::format("Volume: {}%", (uint8_t)volume).c_str());
|
volume_slider->SetLabel(fmt::format("Volume: {}%", (int)volume).c_str());
|
||||||
if (!pitch_clicked) pitch_slider->SetValueDouble(pitch);
|
if (!pitch_clicked) pitch_slider->SetValueDouble(pitch);
|
||||||
pitch_slider->SetLabel(fmt::format("Pitch {:.02f}x", pitch).c_str());
|
pitch_slider->SetLabel(fmt::format("Pitch {:.02f}x", pitch).c_str());
|
||||||
if (!speed_clicked) speed_slider->SetValueDouble(speed);
|
if (!speed_clicked) speed_slider->SetValueDouble(speed);
|
||||||
|
|
|
@ -14,18 +14,8 @@
|
||||||
#include "slider.h"
|
#include "slider.h"
|
||||||
#include "prefs.h"
|
#include "prefs.h"
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <util.hpp>
|
||||||
#define CMD_UPDATE_LABEL_SETTING 0x1000
|
#define CMD_UPDATE_LABEL_SETTING 0x1000
|
||||||
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);
|
|
||||||
};
|
|
||||||
extern bool show_labels;
|
extern bool show_labels;
|
||||||
extern bool show_icons;
|
extern bool show_icons;
|
||||||
extern bool quitting;
|
extern bool quitting;
|
||||||
|
|
|
@ -107,7 +107,7 @@ void PrefsWindow::MessageReceived(BMessage *msg) {
|
||||||
case CMD_REVERT: {
|
case CMD_REVERT: {
|
||||||
set_options_changed(false);
|
set_options_changed(false);
|
||||||
new_label_setting = get_option<std::string>("ui.haiku.label_setting", "icons");
|
new_label_setting = get_option<std::string>("ui.haiku.label_setting", "icons");
|
||||||
new_frontend = get_option<std::string>("ui.frontend", "haiki");
|
new_frontend = get_option<std::string>("ui.frontend", "haiku");
|
||||||
if (new_frontend != "haiku") restart_warning->Show();
|
if (new_frontend != "haiku") restart_warning->Show();
|
||||||
else restart_warning->Hide();
|
else restart_warning->Hide();
|
||||||
for (size_t i = 0; i < backend_ids.size(); i++) {
|
for (size_t i = 0; i < backend_ids.size(); i++) {
|
||||||
|
|
13
backends/ui/qt/CMakeLists.txt
Normal file
13
backends/ui/qt/CMakeLists.txt
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
set(BACKEND_QT_SRC_BASE main.cpp main.h main_window.cpp main_window.h slider.hpp slider.cpp preferences.h preferences.cpp aboutwindow.h aboutwindow.cpp)
|
||||||
|
set(BACKEND_QT_SRC )
|
||||||
|
foreach(SRC IN ITEMS ${BACKEND_QT_SRC_BASE})
|
||||||
|
set(BACKEND_QT_SRC ${BACKEND_QT_SRC} ${CMAKE_CURRENT_SOURCE_DIR}/${SRC})
|
||||||
|
endforeach()
|
||||||
|
set(BACKEND_QT_INC ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
add_ui_backend(qt_ui ${BACKEND_QT_SRC})
|
||||||
|
find_package(Qt6 COMPONENTS Core Gui Widgets)
|
||||||
|
target_link_libraries(qt_ui PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets SDL2::SDL2 fmt::fmt liblooper)
|
||||||
|
target_include_directories(qt_ui PRIVATE ../../..)
|
||||||
|
#target_link_libraries(qt_ui PRIVATE PkgConfig::GTK4 PkgConfig::gtkmm4)
|
||||||
|
#target_include_directories(qt_ui PRIVATE ${BACKEND_QT_INC})
|
66
backends/ui/qt/aboutwindow.cpp
Normal file
66
backends/ui/qt/aboutwindow.cpp
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
#include "aboutwindow.h"
|
||||||
|
QModelIndex LicenseModel::index(int row, int column, const QModelIndex &parent) const {
|
||||||
|
return createIndex(row, 0, row);
|
||||||
|
}
|
||||||
|
QModelIndex LicenseModel::parent(const QModelIndex &child) const {
|
||||||
|
return QModelIndex();
|
||||||
|
}
|
||||||
|
Qt::ItemFlags LicenseModel::flags(const QModelIndex &index) const {
|
||||||
|
return Qt::ItemFlag::ItemIsEnabled|Qt::ItemFlag::ItemIsSelectable;
|
||||||
|
}
|
||||||
|
int LicenseModel::rowCount(const QModelIndex &parent) const {
|
||||||
|
return parent.isValid() ? 0 : licenseData.size();
|
||||||
|
}
|
||||||
|
int LicenseModel::columnCount(const QModelIndex &parent) const {
|
||||||
|
return parent.isValid() ? 0 : 1;
|
||||||
|
}
|
||||||
|
QVariant LicenseModel::data(const QModelIndex &index, int role) const {
|
||||||
|
if ((index.row() < 0 || licenseData.size() <= index.row()) && (role == Qt::DisplayRole || role == Qt::UserRole)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
switch (role) {
|
||||||
|
case Qt::DisplayRole: {
|
||||||
|
const LicenseData &data = licenseData[index.row()];
|
||||||
|
return QString::asprintf("%s (%s)", data.Project.c_str(), data.Spdx.c_str());
|
||||||
|
} break;
|
||||||
|
case Qt::UserRole: {
|
||||||
|
return QString(licenseData[index.row()].LicenseContents.c_str());
|
||||||
|
} break;
|
||||||
|
default: {
|
||||||
|
return QVariant();
|
||||||
|
} break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LicenseModel::LicenseModel() {
|
||||||
|
auto tmp = get_license_data();
|
||||||
|
for (auto data : tmp) {
|
||||||
|
licenseData.push_back(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LicenseModel::~LicenseModel() {
|
||||||
|
|
||||||
|
}
|
||||||
|
AboutWindow::AboutWindow() {
|
||||||
|
license_text = new QTextBrowser();
|
||||||
|
license_list = new QListView();
|
||||||
|
license_list->setModel(new LicenseModel());
|
||||||
|
QObject::connect(license_list, &QListView::clicked, [=,this](const QModelIndex &idx) {
|
||||||
|
license_text->setText(license_list->model()->data(idx, Qt::UserRole).toString());
|
||||||
|
});
|
||||||
|
license_list->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||||
|
license_list->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||||
|
QBoxLayout *mainLayout = new QBoxLayout(QBoxLayout::TopToBottom);
|
||||||
|
QLabel *title = new QLabel("Looper");
|
||||||
|
auto font = title->font();
|
||||||
|
font.setPointSize(24);
|
||||||
|
title->setFont(font);
|
||||||
|
mainLayout->addWidget(title);
|
||||||
|
QLabel *versionText = new QLabel(TAG);
|
||||||
|
mainLayout->addWidget(versionText);
|
||||||
|
QSplitter *splitter = new QSplitter(Qt::Orientation::Horizontal);
|
||||||
|
splitter->addWidget(license_list);
|
||||||
|
splitter->addWidget(license_text);
|
||||||
|
mainLayout->addWidget(splitter);
|
||||||
|
setLayout(mainLayout);
|
||||||
|
license_list->clicked(license_list->model()->index(0, 0));
|
||||||
|
}
|
33
backends/ui/qt/aboutwindow.h
Normal file
33
backends/ui/qt/aboutwindow.h
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
#pragma once
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QTextBrowser>
|
||||||
|
#include <string>
|
||||||
|
#include <QListView>
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <QList>
|
||||||
|
#include <QBoxLayout>
|
||||||
|
#include <QSplitter>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <license.hpp>
|
||||||
|
|
||||||
|
class LicenseModel : public QAbstractListModel {
|
||||||
|
Q_OBJECT;
|
||||||
|
std::vector<LicenseData> licenseData;
|
||||||
|
public:
|
||||||
|
~LicenseModel() override;
|
||||||
|
Q_INVOKABLE Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||||
|
Q_INVOKABLE QModelIndex index(int row, int column, const QModelIndex &parent) const override;
|
||||||
|
Q_INVOKABLE QModelIndex parent(const QModelIndex &child) const override;
|
||||||
|
Q_INVOKABLE int rowCount(const QModelIndex &parent) const override;
|
||||||
|
Q_INVOKABLE int columnCount(const QModelIndex &parent) const override;
|
||||||
|
Q_INVOKABLE QVariant data(const QModelIndex &index, int role) const override;
|
||||||
|
LicenseModel();
|
||||||
|
};
|
||||||
|
class AboutWindow : public QWidget {
|
||||||
|
Q_OBJECT;
|
||||||
|
public:
|
||||||
|
QListView *license_list;
|
||||||
|
QTextBrowser *license_text;
|
||||||
|
public:
|
||||||
|
AboutWindow();
|
||||||
|
};
|
19
backends/ui/qt/main.cpp
Normal file
19
backends/ui/qt/main.cpp
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
#include "main.h"
|
||||||
|
#include "main_window.h"
|
||||||
|
#include <QMainWindow>
|
||||||
|
#include <QWindow>
|
||||||
|
#include <QGuiApplication>
|
||||||
|
#include <QApplication>
|
||||||
|
std::string QtUIBackend::get_id() {
|
||||||
|
return "qt";
|
||||||
|
}
|
||||||
|
std::string QtUIBackend::get_name() {
|
||||||
|
return "QT";
|
||||||
|
}
|
||||||
|
int QtUIBackend::run(std::vector<std::string> args, int argc, char **argv) {
|
||||||
|
UIBackend::run(args, argc, argv);
|
||||||
|
QApplication app(argc, argv);
|
||||||
|
LooperWindow window(this->playback);
|
||||||
|
window.show();
|
||||||
|
return app.exec();
|
||||||
|
}
|
10
backends/ui/qt/main.h
Normal file
10
backends/ui/qt/main.h
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
#pragma once
|
||||||
|
#include <backend.hpp>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
class QtUIBackend : public UIBackend {
|
||||||
|
public:
|
||||||
|
std::string get_id() override;
|
||||||
|
std::string get_name() override;
|
||||||
|
int run(std::vector<std::string> args, int argc, char **argv) override;
|
||||||
|
};
|
167
backends/ui/qt/main_window.cpp
Normal file
167
backends/ui/qt/main_window.cpp
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
#include "main_window.h"
|
||||||
|
#include <QMenuBar>
|
||||||
|
#include "preferences.h"
|
||||||
|
void LooperWindow::Pulse() {
|
||||||
|
auto len = playback->GetLength();
|
||||||
|
auto pos = playback->GetPosition();
|
||||||
|
this->slider->SetLimits(0.0, len);
|
||||||
|
if (!this->slider->IsPressed()) this->slider->SetValue(pos);
|
||||||
|
auto component_count = TimeToComponentCount(len);
|
||||||
|
bool enable_ui = !playback->IsStopped();
|
||||||
|
if (enable_ui) {
|
||||||
|
slider->SetLabel(fmt::format("Position: {}", TimeToString(pos, component_count)).c_str());
|
||||||
|
slider->SetLimitLabels(TimeToString(0, component_count).c_str(), TimeToString(len).c_str());
|
||||||
|
} else {
|
||||||
|
slider->SetLabel("Position");
|
||||||
|
slider->SetLimitLabels("N/A", "N/A");
|
||||||
|
}
|
||||||
|
update_label_setting(labels_visible, icons_visible);
|
||||||
|
slider->setEnabled(enable_ui && len >= 0.0001);
|
||||||
|
pause_resume_btn->setEnabled(enable_ui);
|
||||||
|
stop_btn->setEnabled(enable_ui);
|
||||||
|
restart_btn->setEnabled(enable_ui);
|
||||||
|
auto volume = playback->GetVolume();
|
||||||
|
auto pitch = playback->GetPitch();
|
||||||
|
auto speed = playback->GetSpeed();
|
||||||
|
auto tempo = playback->GetTempo();
|
||||||
|
if (!volume_slider->IsPressed()) volume_slider->SetValue(volume);
|
||||||
|
if (!pitch_slider->IsPressed()) pitch_slider->SetValue(pitch);
|
||||||
|
if (!speed_slider->IsPressed()) speed_slider->SetValue(speed);
|
||||||
|
if (!tempo_slider->IsPressed()) tempo_slider->SetValue(tempo);
|
||||||
|
volume_slider->SetLabel(fmt::format("Volume: {}%", (int)volume).c_str());
|
||||||
|
pitch_slider->SetLabel(fmt::format("Pitch {:.02f}x", pitch).c_str());
|
||||||
|
speed_slider->SetLabel(fmt::format("Speed: {:.02f}x", speed).c_str());
|
||||||
|
tempo_slider->SetLabel(fmt::format("Tempo: {:.02f}x", tempo).c_str());
|
||||||
|
}
|
||||||
|
LooperWindow::LooperWindow(Playback *playback) : QMainWindow() {
|
||||||
|
labels_visible = false;
|
||||||
|
icons_visible = true;
|
||||||
|
this->playback = playback;
|
||||||
|
this->root_layout = new QBoxLayout(QBoxLayout::TopToBottom);
|
||||||
|
QWidget *central_widget = new QWidget();
|
||||||
|
central_widget->setLayout(this->root_layout);
|
||||||
|
this->setCentralWidget(central_widget);
|
||||||
|
prefs_window = new PrefsWindow();
|
||||||
|
about_window = new AboutWindow();
|
||||||
|
QMenuBar *bar = this->menuBar();
|
||||||
|
file_menu = new QMenu("File");
|
||||||
|
open_item = new QAction("Open...");
|
||||||
|
file_dialog = new QFileDialog(this);
|
||||||
|
QObject::connect(file_dialog, &QFileDialog::fileSelected, [=,this](const QString &file) {
|
||||||
|
playback->Start(file.toUtf8().constData());
|
||||||
|
});
|
||||||
|
QObject::connect(open_item, &QAction::triggered, [=,this]() {
|
||||||
|
file_dialog->show();
|
||||||
|
});
|
||||||
|
prefs_item = new QAction("Preferences...");
|
||||||
|
QObject::connect(prefs_item, &QAction::triggered, [=,this]() {
|
||||||
|
prefs_window->show();
|
||||||
|
});
|
||||||
|
quit_item = new QAction("Quit");
|
||||||
|
QObject::connect(quit_item, &QAction::triggered, [=,this]() {
|
||||||
|
qApp->quit();
|
||||||
|
});
|
||||||
|
file_menu->addAction(open_item);
|
||||||
|
file_menu->addAction(prefs_item);
|
||||||
|
file_menu->addAction(quit_item);
|
||||||
|
help_menu = new QMenu("Help");
|
||||||
|
about_item = new QAction("About...");
|
||||||
|
QObject::connect(about_item, &QAction::triggered, [=,this]() {
|
||||||
|
about_window->show();
|
||||||
|
});
|
||||||
|
help_menu->addAction(about_item);
|
||||||
|
bar->addMenu(file_menu);
|
||||||
|
bar->addMenu(help_menu);
|
||||||
|
root_layout->addWidget(bar);
|
||||||
|
QSpacerItem *spacer = new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::MinimumExpanding);
|
||||||
|
root_layout->addSpacerItem(spacer);
|
||||||
|
QBoxLayout *top_row = new QBoxLayout(QBoxLayout::LeftToRight);
|
||||||
|
pause_resume_btn = new QPushButton("Pause");
|
||||||
|
QObject::connect(pause_resume_btn, &QPushButton::pressed, [=,this]() {
|
||||||
|
playback->Pause();
|
||||||
|
});
|
||||||
|
restart_btn = new QPushButton("Restart");
|
||||||
|
QObject::connect(restart_btn, &QPushButton::pressed, [=,this]() {
|
||||||
|
playback->Seek(0.0);
|
||||||
|
});
|
||||||
|
top_row->addWidget(pause_resume_btn);
|
||||||
|
top_row->addWidget(restart_btn);
|
||||||
|
slider = new LooperSlider("seek", "Position", 0.0, 1.0, 0.00000000001, false);
|
||||||
|
slider->DisableModeButton();
|
||||||
|
slider->UseSlider();
|
||||||
|
QObject::connect(slider, &LooperSlider::changed, [=,this](double value) {
|
||||||
|
playback->Seek(value);
|
||||||
|
});
|
||||||
|
top_row->addWidget(slider);
|
||||||
|
stop_btn = new QPushButton("Stop");
|
||||||
|
QObject::connect(stop_btn, &QPushButton::pressed, [=,this]() {
|
||||||
|
playback->Stop();
|
||||||
|
});
|
||||||
|
top_row->addWidget(stop_btn);
|
||||||
|
volume_slider = new LooperSlider("volume", "Volume", 0.0, 100.0, 1.0, false);
|
||||||
|
volume_slider->SetLimitLabels("Muted", "Full Volume");
|
||||||
|
QObject::connect(volume_slider, &LooperSlider::changed, [=,this](double value) {
|
||||||
|
playback->SetVolume(value);
|
||||||
|
});
|
||||||
|
top_row->addWidget(volume_slider);
|
||||||
|
QWidget *top_row_widget = new QWidget();
|
||||||
|
top_row_widget->setLayout(top_row);
|
||||||
|
root_layout->addWidget(top_row_widget);
|
||||||
|
QBoxLayout *bottom_row = new QBoxLayout(QBoxLayout::LeftToRight);
|
||||||
|
speed_slider = new LooperSlider("speed", "Speed", 0.25, 4.0, 0.01, true);
|
||||||
|
pitch_slider = new LooperSlider("pitch", "Pitch", 0.25, 4.0, 0.01, true);
|
||||||
|
tempo_slider = new LooperSlider("tempo", "Tempo", 0.25, 4.0, 0.01, true);
|
||||||
|
speed_slider->SetLimitLabels("0.25x", "4.00x");
|
||||||
|
pitch_slider->SetLimitLabels("0.25x", "4.00x");
|
||||||
|
tempo_slider->SetLimitLabels("0.25x", "4.00x");
|
||||||
|
QObject::connect(speed_slider, &LooperSlider::changed, [=,this](double value) {
|
||||||
|
playback->SetSpeed(value);
|
||||||
|
});
|
||||||
|
QObject::connect(pitch_slider, &LooperSlider::changed, [=,this](double value) {
|
||||||
|
playback->SetPitch(value);
|
||||||
|
});
|
||||||
|
QObject::connect(tempo_slider, &LooperSlider::changed, [=,this](double value) {
|
||||||
|
playback->SetTempo(value);
|
||||||
|
});
|
||||||
|
bottom_row->addWidget(speed_slider);
|
||||||
|
bottom_row->addWidget(pitch_slider);
|
||||||
|
bottom_row->addWidget(tempo_slider);
|
||||||
|
QWidget *bottom_row_widget = new QWidget();
|
||||||
|
bottom_row_widget->setLayout(bottom_row);
|
||||||
|
root_layout->addWidget(bottom_row_widget);
|
||||||
|
QTimer *timer = new QTimer(this);
|
||||||
|
QObject::connect(timer, &QTimer::timeout, [=,this]() {
|
||||||
|
Pulse();
|
||||||
|
});
|
||||||
|
timer->setTimerType(Qt::TimerType::PreciseTimer);
|
||||||
|
timer->setSingleShot(false);
|
||||||
|
timer->setInterval(1);
|
||||||
|
timer->start();
|
||||||
|
QObject::connect(prefs_window, &PrefsWindow::settings_changed, this, &LooperWindow::update_label_setting);
|
||||||
|
//setLayout(layout);
|
||||||
|
}
|
||||||
|
void LooperWindow::update_label_setting(bool labels_visible, bool icons_visible) {
|
||||||
|
this->labels_visible = labels_visible;
|
||||||
|
this->icons_visible = icons_visible;
|
||||||
|
for (auto *slider : {volume_slider, speed_slider, tempo_slider, pitch_slider, slider}) {
|
||||||
|
slider->SetShowButtonIcon(icons_visible);
|
||||||
|
slider->SetShowButtonText(labels_visible);
|
||||||
|
}
|
||||||
|
QString strings[] = {"Restart", "Stop", (playback->IsPaused() && !playback->IsStopped()) ? "Resume" : "Pause"};
|
||||||
|
QIcon icons[] = {QIcon::fromTheme("view-refresh"), QIcon::fromTheme("media-playback-stop"), QIcon::fromTheme((playback->IsPaused() && !playback->IsStopped()) ? "media-playback-start" : "media-playback-pause")};
|
||||||
|
QPushButton *buttons[] = {restart_btn, stop_btn, pause_resume_btn};
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
QIcon icon = icons[i];
|
||||||
|
if (icon.isNull() || labels_visible) {
|
||||||
|
buttons[i]->setText(strings[i]);
|
||||||
|
} else {
|
||||||
|
buttons[i]->setText("");
|
||||||
|
}
|
||||||
|
if (icons_visible && !icon.isNull()) {
|
||||||
|
buttons[i]->setIcon(icons[i]);
|
||||||
|
} else {
|
||||||
|
QIcon emptyIcon;
|
||||||
|
buttons[i]->setIcon(emptyIcon);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
backends/ui/qt/main_window.h
Normal file
49
backends/ui/qt/main_window.h
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
#pragma once
|
||||||
|
#include "playback.h"
|
||||||
|
#include "util.hpp"
|
||||||
|
#include <QMainWindow>
|
||||||
|
#include <QSlider>
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QTextEdit>
|
||||||
|
#include <QBoxLayout>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QFileDialog>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <util.hpp>
|
||||||
|
#include "preferences.h"
|
||||||
|
#include "aboutwindow.h"
|
||||||
|
#include "slider.hpp"
|
||||||
|
class LooperWindow : public QMainWindow {
|
||||||
|
Q_OBJECT;
|
||||||
|
bool labels_visible;
|
||||||
|
bool icons_visible;
|
||||||
|
LooperSlider *volume_slider;
|
||||||
|
LooperSlider *speed_slider;
|
||||||
|
LooperSlider *tempo_slider;
|
||||||
|
LooperSlider *pitch_slider;
|
||||||
|
std::thread *update_thread = nullptr;
|
||||||
|
bool done = false;
|
||||||
|
void Pulse();
|
||||||
|
void ThreadFunc();
|
||||||
|
const char *file_to_play = nullptr;
|
||||||
|
QPushButton *restart_btn;
|
||||||
|
QPushButton *stop_btn;
|
||||||
|
QPushButton *pause_resume_btn;
|
||||||
|
Playback *playback;
|
||||||
|
LooperSlider *slider;
|
||||||
|
QMenu *file_menu;
|
||||||
|
QAction *open_item;
|
||||||
|
QAction *prefs_item;
|
||||||
|
QAction *quit_item;
|
||||||
|
QMenu *help_menu;
|
||||||
|
QAction *about_item;
|
||||||
|
QFileDialog *file_dialog;
|
||||||
|
QBoxLayout *root_layout;
|
||||||
|
void update_label_setting(bool labels_visible, bool icons_visible);
|
||||||
|
public:
|
||||||
|
AboutWindow *about_window;
|
||||||
|
PrefsWindow *prefs_window;
|
||||||
|
explicit LooperWindow(Playback *playback);
|
||||||
|
};
|
95
backends/ui/qt/preferences.cpp
Normal file
95
backends/ui/qt/preferences.cpp
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
#include "preferences.h"
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QBoxLayout>
|
||||||
|
#include <backend.hpp>
|
||||||
|
#include <options.hpp>
|
||||||
|
using namespace Looper::Options;
|
||||||
|
PrefsWindow::PrefsWindow() {
|
||||||
|
auto *root_layout = new QBoxLayout(QBoxLayout::TopToBottom);
|
||||||
|
this->setLayout(root_layout);
|
||||||
|
restart_warning = new QLabel("A restart is needed to apply some changes.");
|
||||||
|
restart_warning->hide();
|
||||||
|
root_layout->addWidget(restart_warning);
|
||||||
|
frontend_btn = new QPushButton();
|
||||||
|
frontend_menu = new QMenu();
|
||||||
|
for (auto &kv : UIBackend::backends) {
|
||||||
|
UIBackend *backend = kv.second;
|
||||||
|
const char *name = strdup(backend->get_name().c_str());
|
||||||
|
QAction *action = new QAction(name);
|
||||||
|
action->connect(action, &QAction::triggered, [=,this]() {
|
||||||
|
this->new_frontend = backend->get_id();
|
||||||
|
frontend_btn->setText(strdup(this->new_frontend.c_str()));
|
||||||
|
this->set_options_changed(true);
|
||||||
|
});
|
||||||
|
frontend_menu->addAction(action);
|
||||||
|
frontend_options.push_back(action);
|
||||||
|
}
|
||||||
|
frontend_btn->setMenu(frontend_menu);
|
||||||
|
root_layout->addWidget(frontend_btn);
|
||||||
|
QFrame *frame = new QFrame();
|
||||||
|
frame->setWindowTitle("Labels and Icons");
|
||||||
|
auto *label_settings_group = new QBoxLayout(QBoxLayout::TopToBottom);
|
||||||
|
frame->setLayout(label_settings_group);
|
||||||
|
labels_only = new QRadioButton("Labels Only");
|
||||||
|
labels_only->connect(labels_only, &QRadioButton::pressed, [=,this]() {
|
||||||
|
this->new_label_setting = "labels";
|
||||||
|
this->set_options_changed(true);
|
||||||
|
});
|
||||||
|
icons_only = new QRadioButton("Icons Only");
|
||||||
|
icons_only->connect(icons_only, &QRadioButton::pressed, [=,this]() {
|
||||||
|
this->new_label_setting = "icons";
|
||||||
|
this->set_options_changed(true);
|
||||||
|
});
|
||||||
|
both_labels_icons = new QRadioButton("Both");
|
||||||
|
both_labels_icons->connect(both_labels_icons, &QRadioButton::pressed, [=,this]() {
|
||||||
|
this->new_label_setting = "both";
|
||||||
|
this->set_options_changed(true);
|
||||||
|
});
|
||||||
|
label_settings_group->addWidget(labels_only);
|
||||||
|
label_settings_group->addWidget(icons_only);
|
||||||
|
label_settings_group->addWidget(both_labels_icons);
|
||||||
|
root_layout->addWidget(frame);
|
||||||
|
revert_btn = new QPushButton("Revert");
|
||||||
|
QObject::connect(revert_btn, &QPushButton::pressed, this, &PrefsWindow::revert);
|
||||||
|
apply_btn = new QPushButton("Apply");
|
||||||
|
QObject::connect(apply_btn, &QPushButton::pressed, this, &PrefsWindow::apply);
|
||||||
|
QWidget *btn_view = new QWidget();
|
||||||
|
QBoxLayout *btn_box = new QBoxLayout(QBoxLayout::LeftToRight);
|
||||||
|
btn_view->setLayout(btn_box);
|
||||||
|
btn_box->addWidget(revert_btn);
|
||||||
|
btn_box->addWidget(apply_btn);
|
||||||
|
root_layout->addWidget(btn_view);
|
||||||
|
revert();
|
||||||
|
}
|
||||||
|
void PrefsWindow::set_options_changed(bool changed) {
|
||||||
|
this->revert_btn->setEnabled(changed);
|
||||||
|
this->apply_btn->setEnabled(changed);
|
||||||
|
}
|
||||||
|
void PrefsWindow::update_label_setting() {
|
||||||
|
bool labels_enabled = true;
|
||||||
|
bool icons_enabled = true;
|
||||||
|
if (new_label_setting == "icons") {
|
||||||
|
labels_enabled = false;
|
||||||
|
} else if (new_label_setting == "labels") {
|
||||||
|
icons_enabled = false;
|
||||||
|
}
|
||||||
|
emit(settings_changed(labels_enabled, icons_enabled));
|
||||||
|
}
|
||||||
|
void PrefsWindow::revert() {
|
||||||
|
set_options_changed(false);
|
||||||
|
new_label_setting = get_option<std::string>("ui.label_setting", "icons");
|
||||||
|
new_frontend = get_option<std::string>("ui.frontend", "qt");
|
||||||
|
if (new_frontend != "qt") restart_warning->show();
|
||||||
|
else restart_warning->hide();
|
||||||
|
frontend_btn->setText(new_frontend.c_str());
|
||||||
|
update_label_setting();
|
||||||
|
}
|
||||||
|
void PrefsWindow::apply() {
|
||||||
|
set_options_changed(false);
|
||||||
|
set_option<std::string>("ui.label_setting", new_label_setting);
|
||||||
|
set_option<std::string>("ui.frontend", new_frontend);
|
||||||
|
if (new_frontend != "qt") restart_warning->show();
|
||||||
|
else restart_warning->hide();
|
||||||
|
frontend_btn->setText(new_frontend.c_str());
|
||||||
|
update_label_setting();
|
||||||
|
}
|
35
backends/ui/qt/preferences.h
Normal file
35
backends/ui/qt/preferences.h
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#pragma once
|
||||||
|
#include <QWindow>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QCheckBox>
|
||||||
|
#include <QRadioButton>
|
||||||
|
#include <QBoxLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
#include <map>
|
||||||
|
class PrefsWindow : public QWidget {
|
||||||
|
Q_OBJECT;
|
||||||
|
std::vector<std::string> backend_ids;
|
||||||
|
int cur_option = 0;
|
||||||
|
QLabel *restart_warning;
|
||||||
|
std::string new_label_setting;
|
||||||
|
std::string new_frontend;
|
||||||
|
QPushButton *frontend_btn;
|
||||||
|
QMenu *frontend_menu;
|
||||||
|
std::vector<QAction*> frontend_options;
|
||||||
|
QCheckBox *menu_icons;
|
||||||
|
QRadioButton *labels_only;
|
||||||
|
QRadioButton *icons_only;
|
||||||
|
QRadioButton *both_labels_icons;
|
||||||
|
QPushButton *revert_btn;
|
||||||
|
QPushButton *apply_btn;
|
||||||
|
void update_label_setting();
|
||||||
|
void set_options_changed(bool changed);
|
||||||
|
void revert();
|
||||||
|
void apply();
|
||||||
|
public:
|
||||||
|
PrefsWindow();
|
||||||
|
Q_SIGNALS:
|
||||||
|
void settings_changed(bool use_labels, bool use_icons);
|
||||||
|
};
|
278
backends/ui/qt/slider.cpp
Normal file
278
backends/ui/qt/slider.cpp
Normal file
|
@ -0,0 +1,278 @@
|
||||||
|
#include "slider.hpp"
|
||||||
|
LooperSlider::LooperSlider(const char *name, const char *label, double min, double max, double tick, bool logarithmic) : QWidget() {
|
||||||
|
root_layout = new QBoxLayout(QBoxLayout::LeftToRight, this);
|
||||||
|
text_layout_view = new QBoxLayout(QBoxLayout::TopToBottom);
|
||||||
|
QWidget *text_layout_widget = new QWidget();
|
||||||
|
text_layout_widget->setLayout(text_layout_view);
|
||||||
|
root_layout->addWidget(text_layout_widget);
|
||||||
|
text_label = new QLabel();
|
||||||
|
slider = new _looperSlider(Qt::Orientation::Horizontal);
|
||||||
|
slider->connect(slider, &_looperSlider::mousePressed, [=,this]() {
|
||||||
|
pressed = true;
|
||||||
|
slider_value_changed_after_release = false;
|
||||||
|
emit(mousePressed());
|
||||||
|
});
|
||||||
|
slider->connect(slider, &_looperSlider::mouseReleased, [=,this]() {
|
||||||
|
emit(mouseReleased());
|
||||||
|
pressed = false;
|
||||||
|
});
|
||||||
|
slider->connect(slider, &QSlider::valueChanged, [=,this](int value) {
|
||||||
|
if (slider_value_updating) return;
|
||||||
|
double actual_value = value * tick;
|
||||||
|
if (logarithmic) actual_value = scaler->unscale_log(actual_value);
|
||||||
|
this->SetValue(actual_value);
|
||||||
|
emit(changed(actual_value));
|
||||||
|
slider_value_changed_after_release = true;
|
||||||
|
});
|
||||||
|
text_layout_view->addWidget(slider);
|
||||||
|
btn = new QPushButton();
|
||||||
|
btn->connect(btn, &QPushButton::pressed, [=,this]() {
|
||||||
|
SwitchModes();
|
||||||
|
});
|
||||||
|
root_layout->addWidget(btn);
|
||||||
|
text = new QLineEdit();
|
||||||
|
text->setVisible(false);
|
||||||
|
text_layout_view->addWidget(text);
|
||||||
|
text->connect(text, &QLineEdit::textChanged, [=,this]() {
|
||||||
|
const char *value = text->text().toLocal8Bit().constData();
|
||||||
|
bool ok = true;
|
||||||
|
double output = this->Value();
|
||||||
|
try {
|
||||||
|
output = std::stod(value);
|
||||||
|
} catch (std::exception) {
|
||||||
|
ok = false;
|
||||||
|
}
|
||||||
|
if (ok) {
|
||||||
|
textChanged = true;
|
||||||
|
emit(changed(output));
|
||||||
|
this->SetValue(output);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
limits_view = new QBoxLayout(QBoxLayout::LeftToRight);
|
||||||
|
min_label_view = new QLabel();
|
||||||
|
limits_view->addWidget(min_label_view, 0, Qt::Alignment::enum_type::AlignLeft);
|
||||||
|
max_label_view = new QLabel();
|
||||||
|
limits_view->addWidget(max_label_view, 0, Qt::Alignment::enum_type::AlignRight);
|
||||||
|
scaler = new LooperLogScaler(min, max);
|
||||||
|
set_min(min);
|
||||||
|
set_max(max);
|
||||||
|
set_tick(tick);
|
||||||
|
set_logarithmic(logarithmic);
|
||||||
|
this->label = strdup(label);
|
||||||
|
this->value = min;
|
||||||
|
UpdateSlider(true);
|
||||||
|
|
||||||
|
}
|
||||||
|
bool LooperSlider::IsPressed() {
|
||||||
|
return pressed;
|
||||||
|
}
|
||||||
|
double LooperSlider::Value() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
void LooperSlider::set_value(double value) {
|
||||||
|
if (!pressed && slider_value_changed_after_release) this->value = value;
|
||||||
|
this->UpdateSlider(true);
|
||||||
|
}
|
||||||
|
void LooperSlider::SetValue(double value) {
|
||||||
|
set_value(value);
|
||||||
|
}
|
||||||
|
void LooperSlider::set_min(double min) {
|
||||||
|
settings_changed = true;
|
||||||
|
this->min = min;
|
||||||
|
}
|
||||||
|
void LooperSlider::set_max(double max) {
|
||||||
|
settings_changed = true;
|
||||||
|
this->max = max;
|
||||||
|
}
|
||||||
|
void LooperSlider::set_logarithmic(bool logarithmic) {
|
||||||
|
settings_changed = true;
|
||||||
|
this->logarithmic = true;
|
||||||
|
}
|
||||||
|
void LooperSlider::UpdateLogScaler() {
|
||||||
|
this->scaler->update_min_max(this->min, this->max);
|
||||||
|
}
|
||||||
|
void LooperSlider::SetMin(double min) {
|
||||||
|
this->set_min(min);
|
||||||
|
UpdateSlider(false);
|
||||||
|
}
|
||||||
|
void LooperSlider::SetMax(double max) {
|
||||||
|
this->set_max(max);
|
||||||
|
UpdateSlider(false);
|
||||||
|
}
|
||||||
|
double LooperSlider::Min() {
|
||||||
|
return this->min;
|
||||||
|
}
|
||||||
|
double LooperSlider::Max() {
|
||||||
|
return this->max;
|
||||||
|
}
|
||||||
|
void LooperSlider::SetLimits(double min, double max) {
|
||||||
|
set_min(min);
|
||||||
|
set_max(max);
|
||||||
|
UpdateSlider(false);
|
||||||
|
}
|
||||||
|
void LooperSlider::SetMinLabel(const char *label) {
|
||||||
|
if (this->min_label) free((void*)this->min_label);
|
||||||
|
this->min_label = strdup(label);
|
||||||
|
UpdateSlider();
|
||||||
|
}
|
||||||
|
void LooperSlider::SetMaxLabel(const char *label) {
|
||||||
|
if (this->max_label) free((void*)this->max_label);
|
||||||
|
this->max_label = strdup(label);
|
||||||
|
UpdateSlider();
|
||||||
|
}
|
||||||
|
void LooperSlider::SetLimitLabels(const char *min, const char *max) {
|
||||||
|
SetMinLabel(min);
|
||||||
|
SetMaxLabel(max);
|
||||||
|
}
|
||||||
|
LooperSlider::~LooperSlider() {
|
||||||
|
if (this->min_label) free((void*)this->min_label);
|
||||||
|
if (this->max_label) free((void*)this->max_label);
|
||||||
|
if (this->label) free((void*)this->label);
|
||||||
|
}
|
||||||
|
const char *LooperSlider::MinLabel() {
|
||||||
|
return this->min_label;
|
||||||
|
}
|
||||||
|
const char *LooperSlider::MaxLabel() {
|
||||||
|
return this->max_label;
|
||||||
|
}
|
||||||
|
void LooperSlider::SetLogarithmic(bool logarithmic) {
|
||||||
|
set_logarithmic(logarithmic);
|
||||||
|
UpdateSlider(false);
|
||||||
|
}
|
||||||
|
bool LooperSlider::IsLogarithmic() {
|
||||||
|
return this->logarithmic;
|
||||||
|
}
|
||||||
|
void LooperSlider::SetLabel(const char *label) {
|
||||||
|
if (this->label) free((void*)this->label);
|
||||||
|
this->label = strdup(label);
|
||||||
|
}
|
||||||
|
const char *LooperSlider::Label() {
|
||||||
|
return this->label;
|
||||||
|
}
|
||||||
|
double LooperSlider::Tick() {
|
||||||
|
return this->tick;
|
||||||
|
}
|
||||||
|
void LooperSlider::SetMode(bool use_text_editor) {
|
||||||
|
this->text_edit_mode = use_text_editor;
|
||||||
|
UpdateSlider(true);
|
||||||
|
}
|
||||||
|
void LooperSlider::EnableModeButton(bool enabled) {
|
||||||
|
btn->setVisible(enabled);
|
||||||
|
}
|
||||||
|
void LooperSlider::SetTick(double value) {
|
||||||
|
set_tick(value);
|
||||||
|
UpdateSlider(false);
|
||||||
|
}
|
||||||
|
void LooperSlider::set_tick(double value) {
|
||||||
|
this->settings_changed = true;
|
||||||
|
this->tick = value;
|
||||||
|
}
|
||||||
|
void LooperSlider::UpdateSlider(bool update_value) {
|
||||||
|
UpdateLogScaler();
|
||||||
|
if (this->min_label == NULL) {
|
||||||
|
this->min_label_view->setText("");
|
||||||
|
} else {
|
||||||
|
this->min_label_view->setText(this->min_label);
|
||||||
|
}
|
||||||
|
if (this->max_label == NULL) {
|
||||||
|
this->max_label_view->setText("");
|
||||||
|
} else {
|
||||||
|
this->max_label_view->setText(this->max_label);
|
||||||
|
}
|
||||||
|
limits_visible = (this->min_label == NULL && this->max_label == NULL);
|
||||||
|
if (limits_visible) {
|
||||||
|
if (!limits_actually_visible) {
|
||||||
|
this->text_layout_view->addItem(this->limits_view);
|
||||||
|
limits_actually_visible = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (limits_actually_visible) {
|
||||||
|
this->text_layout_view->removeItem(this->limits_view);
|
||||||
|
limits_actually_visible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this->label == NULL) {
|
||||||
|
if (text_label_visible) {
|
||||||
|
this->text_layout_view->removeWidget(this->text_label);
|
||||||
|
text_label_visible = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this->text_label->setText(this->label);
|
||||||
|
if (!text_label_visible) {
|
||||||
|
this->text_layout_view->insertWidget(0, this->text_label);
|
||||||
|
text_label_visible = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this->text_edit_mode) {
|
||||||
|
if (!text_editor_visible) {
|
||||||
|
this->text_layout_view->insertWidget(1, this->text);
|
||||||
|
text_editor_visible = true;
|
||||||
|
}
|
||||||
|
if (slider_visible) {
|
||||||
|
this->text_layout_view->removeWidget(this->slider);
|
||||||
|
slider_visible = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!slider_visible) {
|
||||||
|
this->text_layout_view->insertWidget(1, this->slider);
|
||||||
|
slider_visible = true;
|
||||||
|
}
|
||||||
|
if (text_editor_visible) {
|
||||||
|
this->text_layout_view->removeWidget(this->text);
|
||||||
|
text_editor_visible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto icon = QIcon::fromTheme(text_edit_mode ? "slider_edit_mode" : "text_edit_mode", QIcon::fromTheme(text_edit_mode ? "dialog-ok" : "edit"));
|
||||||
|
auto text = text_edit_mode ? "Imprecise" : "Precise";
|
||||||
|
if (icon.isNull() || show_button_text) {
|
||||||
|
this->btn->setText(text);
|
||||||
|
} else {
|
||||||
|
this->btn->setText("");
|
||||||
|
}
|
||||||
|
if (show_button_icon && !icon.isNull()) {
|
||||||
|
this->btn->setIcon(icon);
|
||||||
|
} else {
|
||||||
|
QIcon emptyIcon;
|
||||||
|
this->btn->setIcon(emptyIcon);
|
||||||
|
}
|
||||||
|
this->slider->setVisible(slider_visible);
|
||||||
|
this->text->setVisible(text_editor_visible);
|
||||||
|
this->text_label->setVisible(text_label_visible);
|
||||||
|
this->min_label_view->setVisible(limits_actually_visible);
|
||||||
|
this->max_label_view->setVisible(limits_actually_visible);
|
||||||
|
if (update_value) {
|
||||||
|
if (textChanged) {
|
||||||
|
textChanged = false;
|
||||||
|
} else {
|
||||||
|
if (!pressed && !text_edit_mode) this->text->setText(fmt::to_string(this->value).c_str());
|
||||||
|
}
|
||||||
|
slider_value_updating = true;
|
||||||
|
if (!pressed && slider_value_changed_after_release) {
|
||||||
|
this->slider->setValue(this->logarithmic ? (this->scaler->scale_log(this->value) / this->tick) : (this->value / this->tick));
|
||||||
|
this->slider->setMinimum(this->min / tick);
|
||||||
|
this->slider->setMaximum(this->max / tick);
|
||||||
|
}
|
||||||
|
slider_value_updating = false;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool LooperSlider::ShowButtonText() {
|
||||||
|
return show_button_text;
|
||||||
|
}
|
||||||
|
bool LooperSlider::ShowButtonIcon() {
|
||||||
|
return show_button_icon;
|
||||||
|
}
|
||||||
|
void LooperSlider::SetShowButtonIcon(bool enable) {
|
||||||
|
this->show_button_icon = enable;
|
||||||
|
UpdateSlider();
|
||||||
|
}
|
||||||
|
void LooperSlider::SetShowButtonText(bool enable) {
|
||||||
|
this->show_button_text = enable;
|
||||||
|
UpdateSlider();
|
||||||
|
}
|
||||||
|
void LooperSlider::changeEvent(QEvent *event) {
|
||||||
|
if (event->type() == QEvent::EnabledChange) {
|
||||||
|
this->slider->setEnabled(isEnabled());
|
||||||
|
this->text->setEnabled(isEnabled());
|
||||||
|
}
|
||||||
|
}
|
119
backends/ui/qt/slider.hpp
Normal file
119
backends/ui/qt/slider.hpp
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QSlider>
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QBoxLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QEvent>
|
||||||
|
#include <util.hpp>
|
||||||
|
class _looperSlider : public QSlider {
|
||||||
|
Q_OBJECT;
|
||||||
|
protected:
|
||||||
|
inline void mousePressEvent(QMouseEvent *e) override {
|
||||||
|
QSlider::mousePressEvent(e);
|
||||||
|
emit(mousePressed());
|
||||||
|
}
|
||||||
|
inline void mouseReleaseEvent(QMouseEvent *e) override {
|
||||||
|
QSlider::mouseReleaseEvent(e);
|
||||||
|
emit(mouseReleased());
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
inline _looperSlider(Qt::Orientation orientation, QWidget *parent = nullptr) : QSlider(orientation, parent) { }
|
||||||
|
Q_SIGNALS:
|
||||||
|
void mousePressed();
|
||||||
|
void mouseReleased();
|
||||||
|
};
|
||||||
|
class LooperSlider : public QWidget {
|
||||||
|
Q_OBJECT;
|
||||||
|
_looperSlider *slider;
|
||||||
|
QPushButton *btn;
|
||||||
|
QLineEdit *text;
|
||||||
|
bool show_button_text;
|
||||||
|
bool show_button_icon;
|
||||||
|
QLabel *text_label;
|
||||||
|
QLabel *min_label_view;
|
||||||
|
QLabel *max_label_view;
|
||||||
|
QBoxLayout *limits_view;
|
||||||
|
QBoxLayout *root_layout;
|
||||||
|
bool slider_value_updating = false;
|
||||||
|
bool limits_visible = false;
|
||||||
|
bool settings_changed = false;
|
||||||
|
QBoxLayout *text_layout_view;
|
||||||
|
bool pressed = false;
|
||||||
|
LooperLogScaler *scaler;
|
||||||
|
void UpdateLogScaler();
|
||||||
|
void UpdateSlider(bool update_value = false);
|
||||||
|
bool textChanged = false;
|
||||||
|
bool text_editor_visible = false;
|
||||||
|
bool slider_visible = false;
|
||||||
|
bool slider_value_changed_after_release = true;
|
||||||
|
bool text_label_visible = false;
|
||||||
|
bool limits_actually_visible = false;
|
||||||
|
double value;
|
||||||
|
double tick;
|
||||||
|
double min;
|
||||||
|
double max;
|
||||||
|
bool text_edit_mode = false;
|
||||||
|
bool logarithmic;
|
||||||
|
const char *label = NULL;
|
||||||
|
const char *min_label = NULL;
|
||||||
|
const char *max_label = NULL;
|
||||||
|
void set_min(double min);
|
||||||
|
void set_max(double max);
|
||||||
|
void set_tick(double value);
|
||||||
|
void set_logarithmic(bool logarithmic);
|
||||||
|
void set_value(double value);
|
||||||
|
public:
|
||||||
|
bool IsPressed();
|
||||||
|
void SetMin(double min);
|
||||||
|
double Min();
|
||||||
|
void SetMax(double max);
|
||||||
|
double Max();
|
||||||
|
void SetLimits(double min, double max);
|
||||||
|
void SetMinLabel(const char *label);
|
||||||
|
void SetMaxLabel(const char *label);
|
||||||
|
const char *MinLabel();
|
||||||
|
const char *MaxLabel();
|
||||||
|
void SetLimitLabels(const char *min, const char *max);
|
||||||
|
void SetValue(double value);
|
||||||
|
double Value();
|
||||||
|
void SetLogarithmic(bool logarithmic);
|
||||||
|
bool IsLogarithmic();
|
||||||
|
void SetLabel(const char *label);
|
||||||
|
const char *Label();
|
||||||
|
double Tick();
|
||||||
|
bool ShowButtonText();
|
||||||
|
bool ShowButtonIcon();
|
||||||
|
void SetShowButtonText(bool enable);
|
||||||
|
void SetShowButtonIcon(bool enable);
|
||||||
|
void SetTick(double value);
|
||||||
|
void EnableModeButton(bool enabled = true);
|
||||||
|
inline void DisableModeButton() {
|
||||||
|
EnableModeButton(false);
|
||||||
|
}
|
||||||
|
void SetMode(bool use_text_editor);
|
||||||
|
inline void UseTextEditor() {
|
||||||
|
SetMode(true);
|
||||||
|
}
|
||||||
|
inline void UseSlider() {
|
||||||
|
SetMode(false);
|
||||||
|
}
|
||||||
|
inline bool UsingTextEditor() {
|
||||||
|
return text_edit_mode;
|
||||||
|
}
|
||||||
|
inline bool UsingSlider() {
|
||||||
|
return !UsingTextEditor();
|
||||||
|
}
|
||||||
|
inline void SwitchModes() {
|
||||||
|
SetMode(!text_edit_mode);
|
||||||
|
}
|
||||||
|
explicit LooperSlider(const char *name, const char *label, double min, double max, double tick = 0.0001, bool logarithmic = false);
|
||||||
|
~LooperSlider();
|
||||||
|
void changeEvent(QEvent *event) override;
|
||||||
|
Q_SIGNALS:
|
||||||
|
void changed(double value);
|
||||||
|
void mousePressed();
|
||||||
|
void mouseReleased();
|
||||||
|
};
|
4
backends/ui/qt/ui.json
Normal file
4
backends/ui/qt/ui.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"class_name": "QtUIBackend",
|
||||||
|
"include_path": "main.h"
|
||||||
|
}
|
43
cmake/built_sdl/SDL3Config.cmake
Normal file
43
cmake/built_sdl/SDL3Config.cmake
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
# sdl2 cmake project-config input for CMakeLists.txt script
|
||||||
|
|
||||||
|
include(FeatureSummary)
|
||||||
|
set_package_properties(SDL3 PROPERTIES
|
||||||
|
URL "https://www.libsdl.org/"
|
||||||
|
DESCRIPTION "low level access to audio, keyboard, mouse, joystick, and graphics hardware"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
set(SDL3_FOUND TRUE CACHE INTERNAL "")
|
||||||
|
|
||||||
|
set(SDL3_SDL3_FOUND TRUE CACHE INTERNAL "")
|
||||||
|
set(SDL3_SDL3-static_FOUND TRUE CACHE INTERNAL "")
|
||||||
|
set(SDL3_SDL3test_FOUND OFF CACHE INTERNAL "")
|
||||||
|
# Find SDL3::Headers
|
||||||
|
if(NOT TARGET SDL3::Headers)
|
||||||
|
add_library(SDL3::Headers ALIAS SDL3_Headers)
|
||||||
|
endif()
|
||||||
|
set(SDL3_Headers_FOUND TRUE)
|
||||||
|
if (NOT DEFINED SDL3_FOUND)
|
||||||
|
if (SDL2::SDL3main)
|
||||||
|
set(SDL3_SDL3main_FOUND ON)
|
||||||
|
else()
|
||||||
|
set(SDL3_SDL3main_FOUND OFF)
|
||||||
|
endif()
|
||||||
|
set(SDL3_LIBRARY SDL3::SDL3 CACHE INTERNAL "")
|
||||||
|
set(SDL3_INCLUDE_DIR CACHE INTERNAL "")
|
||||||
|
set(SDL3_LIBRARIES SDL3::SDL3 CACHE INTERNAL "")
|
||||||
|
set(SDL3_STATIC_LIBRARIES SDL3::SDL3 CACHE INTERNAL "")
|
||||||
|
set(SDL3_STATIC_PRIVATE_LIBS "" CACHE INTERNAL "")
|
||||||
|
set(SDL3_INCLUDE_DIRS "" CACHE INTERNAL "")
|
||||||
|
#get_target_property(SDL2_STATIC_PRIVATE_LIBS SDL2-static LINK_LIBRARIES)
|
||||||
|
get_target_property(_SDL3_INCLUDE_DIRS SDL3::Headers INCLUDE_DIRECTORIES)
|
||||||
|
set(SDL3_INCLUDE_DIRS ${_SDL3_INCLUDE_DIRS} CACHE INTERNAL "")
|
||||||
|
unset(_SDL3_INCLUDE_DIRS)
|
||||||
|
if(SDL3_SDL3main_FOUND)
|
||||||
|
set(SDL3MAIN_LIBRARY SDL3::SDL3main CACHE INTERNAL "")
|
||||||
|
else()
|
||||||
|
set(SDL3MAIN_LIBRARY CACHE INTERNAL "")
|
||||||
|
endif()
|
||||||
|
endif()
|
50
include/playback.h
Normal file
50
include/playback.h
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
#pragma once
|
||||||
|
#include <glib-object.h>
|
||||||
|
#include <glib.h>
|
||||||
|
#include <gio/gliststore.h>
|
||||||
|
enum {
|
||||||
|
/// @brief No signals have occurred.
|
||||||
|
PlaybackSignalNone = 0,
|
||||||
|
/// @brief The file was changed. Recheck the properties of the file because they are likely different.
|
||||||
|
PlaybackSignalFileChanged = 1 << 0,
|
||||||
|
/// @brief The speed was changed.
|
||||||
|
PlaybackSignalSpeedChanged = 1 << 1,
|
||||||
|
/// @brief The speed was changed.
|
||||||
|
PlaybackSignalTempoChanged = 1 << 2,
|
||||||
|
/// @brief The speed was changed.
|
||||||
|
PlaybackSignalPitchChanged = 1 << 3,
|
||||||
|
/// @brief Playback was paused. If @ref PlaybackSignalResumed has also been sent, you must use @ref Playback::IsPaused to check if playback was paused or resumed.
|
||||||
|
PlaybackSignalPaused = 1 << 4,
|
||||||
|
/// @brief Playback was resumed. If @ref PlaybackSignalPaused has also been sent, you must use @ref Playback::IsPaused to check if playback was paused or resumed.
|
||||||
|
PlaybackSignalResumed = 1 << 5,
|
||||||
|
/// @brief Playback was stopped entirely. If @ref PlaybackSignalStarted has also been signalled, call @ref Playback::IsStopped to find out if playback is currently playing.
|
||||||
|
PlaybackSignalStopped = 1 << 6,
|
||||||
|
/// @brief An error occurred and playback has likely (but not necessarily) stopped. Call @ref Playback::GetError for details.
|
||||||
|
PlaybackSignalErrorOccurred = 1 << 7,
|
||||||
|
/// @brief Playback was seeked by the @ref Playback::Seek function
|
||||||
|
PlaybackSignalSeeked = 1 << 8,
|
||||||
|
/// @brief Playback has started. If @ref PlaybackSignalStopped has also been signalled, call @ref Playback::IsStopped to find out if playback is currently playing.
|
||||||
|
PlaybackSignalStarted = 1 << 9
|
||||||
|
};
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
#define LOOPER_TYPE_STREAM looper_stream_get_type()
|
||||||
|
#define LOOPER_TYPE_PLAYBACK looper_playback_get_type()
|
||||||
|
G_DECLARE_FINAL_TYPE(LooperStream, looper_stream, LOOPER, STREAM, GObject)
|
||||||
|
G_DECLARE_FINAL_TYPE(LooperPlayback, looper_playback, LOOPER, PLAYBACK, GObject)
|
||||||
|
|
||||||
|
LooperPlayback *looper_playback_new(void);
|
||||||
|
void looper_playback_load(LooperPlayback *self, const gchar *file_path);
|
||||||
|
void looper_playback_start(LooperPlayback *self);
|
||||||
|
void looper_playback_set_stream_idx(LooperPlayback *self, gint stream_idx);
|
||||||
|
bool looper_playback_is_paused(LooperPlayback *self);
|
||||||
|
void looper_playback_seek(LooperPlayback *self, gdouble position);
|
||||||
|
gdouble looper_playback_get_length(LooperPlayback *self);
|
||||||
|
gdouble looper_playback_get_position(LooperPlayback *self);
|
||||||
|
const gchar *looper_stream_get_name(LooperStream *self);
|
||||||
|
gdouble looper_stream_get_length(LooperStream *self);
|
||||||
|
gint looper_stream_get_id(LooperStream *self);
|
||||||
|
void looper_stream_set_name(LooperStream *self, const gchar *name);
|
||||||
|
void looper_stream_set_length(LooperStream *self, gdouble length);
|
||||||
|
void looper_stream_set_id(LooperStream *self, gint id);
|
||||||
|
GListStore *looper_playback_get_streams(LooperPlayback *self);
|
||||||
|
G_END_DECLS
|
329
sdl-android-project/.idea/other.xml
Normal file
329
sdl-android-project/.idea/other.xml
Normal file
|
@ -0,0 +1,329 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="direct_access_persist.xml">
|
||||||
|
<option name="deviceSelectionList">
|
||||||
|
<list>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="27" />
|
||||||
|
<option name="brand" value="DOCOMO" />
|
||||||
|
<option name="codename" value="F01L" />
|
||||||
|
<option name="id" value="F01L" />
|
||||||
|
<option name="manufacturer" value="FUJITSU" />
|
||||||
|
<option name="name" value="F-01L" />
|
||||||
|
<option name="screenDensity" value="360" />
|
||||||
|
<option name="screenX" value="720" />
|
||||||
|
<option name="screenY" value="1280" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="28" />
|
||||||
|
<option name="brand" value="DOCOMO" />
|
||||||
|
<option name="codename" value="SH-01L" />
|
||||||
|
<option name="id" value="SH-01L" />
|
||||||
|
<option name="manufacturer" value="SHARP" />
|
||||||
|
<option name="name" value="AQUOS sense2 SH-01L" />
|
||||||
|
<option name="screenDensity" value="480" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2160" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="Lenovo" />
|
||||||
|
<option name="codename" value="TB370FU" />
|
||||||
|
<option name="id" value="TB370FU" />
|
||||||
|
<option name="manufacturer" value="Lenovo" />
|
||||||
|
<option name="name" value="Tab P12" />
|
||||||
|
<option name="screenDensity" value="340" />
|
||||||
|
<option name="screenX" value="1840" />
|
||||||
|
<option name="screenY" value="2944" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="31" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="a51" />
|
||||||
|
<option name="id" value="a51" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="Galaxy A51" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2400" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="akita" />
|
||||||
|
<option name="id" value="akita" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 8a" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2400" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="33" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="b0q" />
|
||||||
|
<option name="id" value="b0q" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="Galaxy S22 Ultra" />
|
||||||
|
<option name="screenDensity" value="600" />
|
||||||
|
<option name="screenX" value="1440" />
|
||||||
|
<option name="screenY" value="3088" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="32" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="bluejay" />
|
||||||
|
<option name="id" value="bluejay" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 6a" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2400" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="caiman" />
|
||||||
|
<option name="id" value="caiman" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 9 Pro" />
|
||||||
|
<option name="screenDensity" value="360" />
|
||||||
|
<option name="screenX" value="960" />
|
||||||
|
<option name="screenY" value="2142" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="comet" />
|
||||||
|
<option name="id" value="comet" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 9 Pro Fold" />
|
||||||
|
<option name="screenDensity" value="390" />
|
||||||
|
<option name="screenX" value="2076" />
|
||||||
|
<option name="screenY" value="2152" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="29" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="crownqlteue" />
|
||||||
|
<option name="id" value="crownqlteue" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="Galaxy Note9" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="2220" />
|
||||||
|
<option name="screenY" value="1080" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="dm3q" />
|
||||||
|
<option name="id" value="dm3q" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="Galaxy S23 Ultra" />
|
||||||
|
<option name="screenDensity" value="600" />
|
||||||
|
<option name="screenX" value="1440" />
|
||||||
|
<option name="screenY" value="3088" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="e1q" />
|
||||||
|
<option name="id" value="e1q" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="Galaxy S24" />
|
||||||
|
<option name="screenDensity" value="480" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2340" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="33" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="felix" />
|
||||||
|
<option name="id" value="felix" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel Fold" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="2208" />
|
||||||
|
<option name="screenY" value="1840" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="felix" />
|
||||||
|
<option name="id" value="felix" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel Fold" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="2208" />
|
||||||
|
<option name="screenY" value="1840" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="33" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="felix_camera" />
|
||||||
|
<option name="id" value="felix_camera" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel Fold (Camera-enabled)" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="2208" />
|
||||||
|
<option name="screenY" value="1840" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="33" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="gts8uwifi" />
|
||||||
|
<option name="id" value="gts8uwifi" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="Galaxy Tab S8 Ultra" />
|
||||||
|
<option name="screenDensity" value="320" />
|
||||||
|
<option name="screenX" value="1848" />
|
||||||
|
<option name="screenY" value="2960" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="husky" />
|
||||||
|
<option name="id" value="husky" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 8 Pro" />
|
||||||
|
<option name="screenDensity" value="390" />
|
||||||
|
<option name="screenX" value="1008" />
|
||||||
|
<option name="screenY" value="2244" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="30" />
|
||||||
|
<option name="brand" value="motorola" />
|
||||||
|
<option name="codename" value="java" />
|
||||||
|
<option name="id" value="java" />
|
||||||
|
<option name="manufacturer" value="Motorola" />
|
||||||
|
<option name="name" value="G20" />
|
||||||
|
<option name="screenDensity" value="280" />
|
||||||
|
<option name="screenX" value="720" />
|
||||||
|
<option name="screenY" value="1600" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="komodo" />
|
||||||
|
<option name="id" value="komodo" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 9 Pro XL" />
|
||||||
|
<option name="screenDensity" value="360" />
|
||||||
|
<option name="screenX" value="1008" />
|
||||||
|
<option name="screenY" value="2244" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="33" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="lynx" />
|
||||||
|
<option name="id" value="lynx" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 7a" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2400" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="31" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="oriole" />
|
||||||
|
<option name="id" value="oriole" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 6" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2400" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="33" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="panther" />
|
||||||
|
<option name="id" value="panther" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 7" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2400" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="q5q" />
|
||||||
|
<option name="id" value="q5q" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="Galaxy Z Fold5" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1812" />
|
||||||
|
<option name="screenY" value="2176" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="samsung" />
|
||||||
|
<option name="codename" value="q6q" />
|
||||||
|
<option name="id" value="q6q" />
|
||||||
|
<option name="manufacturer" value="Samsung" />
|
||||||
|
<option name="name" value="SM-F956B" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1856" />
|
||||||
|
<option name="screenY" value="2160" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="30" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="r11" />
|
||||||
|
<option name="id" value="r11" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel Watch" />
|
||||||
|
<option name="screenDensity" value="320" />
|
||||||
|
<option name="screenX" value="384" />
|
||||||
|
<option name="screenY" value="384" />
|
||||||
|
<option name="type" value="WEAR_OS" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="30" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="redfin" />
|
||||||
|
<option name="id" value="redfin" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 5" />
|
||||||
|
<option name="screenDensity" value="440" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2340" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="shiba" />
|
||||||
|
<option name="id" value="shiba" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 8" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2400" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="33" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="tangorpro" />
|
||||||
|
<option name="id" value="tangorpro" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel Tablet" />
|
||||||
|
<option name="screenDensity" value="320" />
|
||||||
|
<option name="screenX" value="1600" />
|
||||||
|
<option name="screenY" value="2560" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
<PersistentDeviceSelectionData>
|
||||||
|
<option name="api" value="34" />
|
||||||
|
<option name="brand" value="google" />
|
||||||
|
<option name="codename" value="tokay" />
|
||||||
|
<option name="id" value="tokay" />
|
||||||
|
<option name="manufacturer" value="Google" />
|
||||||
|
<option name="name" value="Pixel 9" />
|
||||||
|
<option name="screenDensity" value="420" />
|
||||||
|
<option name="screenX" value="1080" />
|
||||||
|
<option name="screenY" value="2424" />
|
||||||
|
</PersistentDeviceSelectionData>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -1 +1 @@
|
||||||
Subproject commit 720da57baba83b3b1829e20133575e57aa1a8a4f
|
Subproject commit 0379bf3a5d52d8542aec1874677c9df5ff9ba5f9
|
|
@ -1 +1 @@
|
||||||
Subproject commit d144031940543e15423a25ae5a8a74141044862f
|
Subproject commit 6dae7eb4a5c3a169f3e298392bff4680224aa94a
|
17
test.sh
Executable file
17
test.sh
Executable file
|
@ -0,0 +1,17 @@
|
||||||
|
#!/bin/bash
|
||||||
|
source venv/bin/activate
|
||||||
|
PIDFILE="$(mktemp)"
|
||||||
|
looper() {
|
||||||
|
echo $$ > "$PIDFILE"
|
||||||
|
./build/default/looper -m
|
||||||
|
rm "$PIDFILE" -f
|
||||||
|
}
|
||||||
|
./build.sh || exit $?
|
||||||
|
looper &
|
||||||
|
tl &
|
||||||
|
while [ -f "$PIDFILE" ]; do
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
for i in $(jobs -p); do
|
||||||
|
kill -HUP $i
|
||||||
|
done
|
18
util.cpp
18
util.cpp
|
@ -245,3 +245,21 @@ PropertyHint make_hint(std::optional<double> min = {}, std::optional<double> max
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LooperLogScaler::LooperLogScaler(double min, double max) {
|
||||||
|
update_min_max(min, max);
|
||||||
|
}
|
||||||
|
void LooperLogScaler::update_min_max(double min, double max) {
|
||||||
|
x0 = min;
|
||||||
|
x1 = (max - min) / (exp(1.0) - 1.0);
|
||||||
|
la = min;
|
||||||
|
lb = (max - min);
|
||||||
|
// x0 = scale_log(min);
|
||||||
|
// x1 = scale_log(max);
|
||||||
|
}
|
||||||
|
double LooperLogScaler::scale_log(double value) {
|
||||||
|
return (std::log(((value - x0) / x1) + 1.0) * lb) + la;
|
||||||
|
}
|
||||||
|
double LooperLogScaler::unscale_log(double value) {
|
||||||
|
return ((std::exp((value - la) / lb) - 1.0) * x1) + x0;
|
||||||
|
}
|
||||||
|
|
12
util.hpp
12
util.hpp
|
@ -563,3 +563,15 @@ 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, 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, 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 = {});
|
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);
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in a new issue