New Timer input mapping

First working implementation of metronome events mapping to source callbacks
This commit is contained in:
Bruno Herbelin
2024-12-26 18:04:59 +01:00
parent d8a771e24f
commit dbe92512ae
5 changed files with 110 additions and 78 deletions

View File

@@ -321,7 +321,7 @@ Control::Control() : receiver_(nullptr)
for (size_t i = 0; i < INPUT_MAX; ++i) {
input_active[i] = false;
input_values[i] = 0.f;
input_values[i] = 1.f;
}
}
@@ -464,24 +464,27 @@ bool Control::init()
void Control::update()
{
if (glfwJoystickPresent(GLFW_JOYSTICK_1) == GLFW_TRUE) {
// read joystick buttons
int num_buttons = 0;
const unsigned char *state_buttons = glfwGetJoystickButtons(GLFW_JOYSTICK_1, &num_buttons);
// map to Control input array
for (int b = 0; b < num_buttons; ++b) {
input_access_.lock();
for (int b = 0; b < MIN(num_buttons, INPUT_JOYSTICK_COUNT_BUTTON); ++b) {
input_active[INPUT_JOYSTICK_FIRST_BUTTON + b] = state_buttons[b] == GLFW_PRESS;
input_values[INPUT_JOYSTICK_FIRST_BUTTON + b] = state_buttons[b] == GLFW_PRESS ? 1.f : 0.f;
input_access_.unlock();
}
input_access_.unlock();
// read joystick axis
int num_axis = 0;
const float *state_axis = glfwGetJoystickAxes(GLFW_JOYSTICK_1, &num_axis);
for (int a = 0; a < num_axis; ++a) {
input_access_.lock();
for (int a = 0; a < MIN(num_axis, INPUT_JOYSTICK_COUNT_AXIS); ++a) {
input_active[INPUT_JOYSTICK_FIRST_AXIS + a] = ABS(state_axis[a]) > 0.02 ? true : false;
input_values[INPUT_JOYSTICK_FIRST_AXIS + a] = state_axis[a];
}
input_access_.unlock();
}
@@ -498,21 +501,17 @@ void Control::update()
}
}
// draft : react to metronome
// int p = (int) Metronome::manager().phase();
// static bool bip = false;
// static int t = 2;
// if (!bip) {
// if (p + 1 == t){
// g_print("bip");
// bip = true;
// }
// }
// else {
// if (p + 1 != t){
// bip = false;
// }
// }
// React to metronome
uint p = (uint) Metronome::manager().phase();
static uint prev_p = UINT_MAX;
input_access_.lock();
if (prev_p != p) {
input_active[INPUT_TIMER_FIRST + (prev_p > INPUT_TIMER_COUNT ? 0 : prev_p) ] = false;
input_active[INPUT_TIMER_FIRST + p] = true;
prev_p = p;
}
input_access_.unlock();
}
void Control::listen()
@@ -1613,9 +1612,9 @@ std::string Control::inputLabel(uint id)
{
label = std::string( "Multitouch ") + std::to_string(id - INPUT_MULTITOUCH_FIRST);
}
else if ( id >= INPUT_CUSTOM_FIRST && id <= INPUT_CUSTOM_LAST )
else if ( id >= INPUT_TIMER_FIRST && id <= INPUT_TIMER_LAST )
{
label = std::string( "Custom ") + std::to_string(id - INPUT_CUSTOM_FIRST);
label = std::string( "Beat ") + std::to_string(id - INPUT_TIMER_FIRST + 1);
}
return label;

View File

@@ -97,14 +97,17 @@
#define INPUT_JOYSTICK_COUNT 20
#define INPUT_JOYSTICK_LAST 64
#define INPUT_JOYSTICK_FIRST_BUTTON 44
#define INPUT_JOYSTICK_COUNT_BUTTON 15
#define INPUT_JOYSTICK_LAST_BUTTON 58
#define INPUT_JOYSTICK_FIRST_AXIS 59
#define INPUT_JOYSTICK_COUNT_AXIS 6
#define INPUT_JOYSTICK_LAST_AXIS 64
#define INPUT_MULTITOUCH_FIRST 65
#define INPUT_MULTITOUCH_COUNT 16
#define INPUT_MULTITOUCH_LAST 81
#define INPUT_CUSTOM_FIRST 82
#define INPUT_CUSTOM_LAST 99
#define INPUT_TIMER_FIRST 82
#define INPUT_TIMER_COUNT 17
#define INPUT_TIMER_LAST 99
#define INPUT_MAX 100

View File

@@ -47,7 +47,7 @@ InputMappingWindow::InputMappingWindow() : WorkspaceWindow("InputMappingInterfac
ICON_FA_TABLET_ALT " TouchOSC" ,
ICON_FA_GAMEPAD " Gamepad",
ICON_FA_CLOCK " Timer" };
current_input_for_mode = { INPUT_KEYBOARD_FIRST, INPUT_NUMPAD_FIRST, INPUT_MULTITOUCH_FIRST, INPUT_JOYSTICK_FIRST };
current_input_for_mode = { INPUT_KEYBOARD_FIRST, INPUT_NUMPAD_FIRST, INPUT_MULTITOUCH_FIRST, INPUT_JOYSTICK_FIRST, INPUT_TIMER_FIRST };
current_input_ = current_input_for_mode[Settings::application.mapping.mode];
}
@@ -734,14 +734,15 @@ void InputMappingWindow::Render()
// Options for current key
const std::string key = (current_input_ < INPUT_NUMPAD_LAST) ? " Key " : " ";
const std::string keymenu = ICON_FA_HAND_POINT_RIGHT + key + Control::manager().inputLabel(current_input_);
const std::string keymenu = ICON_FA_ARROW_RIGHT + key + Control::manager().inputLabel(current_input_);
if (ImGui::BeginMenu(keymenu.c_str()) )
{
if ( ImGui::MenuItem( ICON_FA_WINDOW_CLOSE " Reset mapping", NULL, false, S->inputAssigned(current_input_) ) )
if ( ImGui::MenuItem(ICON_FA_TIMES " Reset", NULL, false, S->inputAssigned(current_input_) ) )
// remove all source callback of this input
S->deleteInputCallbacks(current_input_);
if (ImGui::BeginMenu(ICON_FA_CLOCK " Metronome", S->inputAssigned(current_input_)))
if (ImGui::BeginMenu(ICON_FA_CLOCK " Metronome",
S->inputAssigned(current_input_) && Settings::application.mapping.mode < 4 ))
{
Metronome::Synchronicity sync = S->inputSynchrony(current_input_);
bool active = sync == Metronome::SYNC_NONE;
@@ -760,7 +761,10 @@ void InputMappingWindow::Render()
}
std::list<uint> models = S->assignedInputs();
if (ImGui::BeginMenu(ICON_FA_COPY " Duplicate", models.size() > 0) )
if (models.empty())
ImGui::TextDisabled(ICON_FA_COPY " Copy from");
else {
if (ImGui::BeginMenu(ICON_FA_COPY " Copy from", models.size() > 0) )
{
for (auto m = models.cbegin(); m != models.cend(); ++m) {
if ( *m != current_input_ ) {
@@ -771,7 +775,7 @@ void InputMappingWindow::Render()
}
ImGui::EndMenu();
}
}
ImGui::EndMenu();
}
ImGui::EndMenuBar();
@@ -783,7 +787,7 @@ void InputMappingWindow::Render()
ImVec2 frame_top = ImGui::GetCursorScreenPos();
// change mode if a key is pressed
for (uint k = INPUT_KEYBOARD_FIRST; k < INPUT_MAX; ++k) {
for (uint k = INPUT_KEYBOARD_FIRST; k < INPUT_TIMER_FIRST; ++k) {
if (Control::manager().inputActive(k)) {
if (k < INPUT_NUMPAD_FIRST)
Settings::application.mapping.mode = 0;
@@ -811,6 +815,7 @@ void InputMappingWindow::Render()
color = ImGui::GetStyle().Colors[ImGuiCol_Text];
color.w /= Settings::application.mapping.disabled ? 2.f : 1.0f;
ImGui::PushStyleColor(ImGuiCol_Text, color);
ImGui::PushStyleColor(ImGuiCol_Header, ImGui::GetColorU32(ImGuiCol_Header, 0.4f));
for (uint ik = INPUT_KEYBOARD_FIRST; ik < INPUT_KEYBOARD_LAST; ++ik){
int i = ik - INPUT_KEYBOARD_FIRST;
@@ -861,7 +866,7 @@ void InputMappingWindow::Render()
draw_list->AddRect(pos, pos + keyLetterIconSize, ImGui::GetColorU32(ImGuiCol_Button), 6.f, ImDrawCornerFlags_All, 0.1f);
}
ImGui::PopStyleColor(2);
ImGui::PopStyleColor(3);
ImGui::PopStyleVar(2);
ImGui::PopFont();
@@ -887,6 +892,7 @@ void InputMappingWindow::Render()
color = ImGui::GetStyle().Colors[ImGuiCol_Text];
color.w /= Settings::application.mapping.disabled ? 2.f : 1.0f;
ImGui::PushStyleColor(ImGuiCol_Text, color);
ImGui::PushStyleColor(ImGuiCol_Header, ImGui::GetColorU32(ImGuiCol_Header, 0.4f));
for (size_t p = 0; p < numpad_inputs.size(); ++p){
uint ik = numpad_inputs[p];
@@ -939,7 +945,7 @@ void InputMappingWindow::Render()
draw_list->AddRect(pos, pos + iconsize, ImGui::GetColorU32(ImGuiCol_Button), 6.f, ImDrawCornerFlags_All, 0.1f);
}
ImGui::PopStyleColor(2);
ImGui::PopStyleColor(3);
ImGui::PopStyleVar(2);
ImGui::PopFont();
@@ -959,6 +965,7 @@ void InputMappingWindow::Render()
color = ImGui::GetStyle().Colors[ImGuiCol_Text];
color.w /= Settings::application.mapping.disabled ? 2.f : 1.0f;
ImGui::PushStyleColor(ImGuiCol_Text, color);
ImGui::PushStyleColor(ImGuiCol_Header, ImGui::GetColorU32(ImGuiCol_Header, 0.4f));
const ImVec2 touch_bar_size = keyNumpadItemSize * ImVec2(0.65f, 0.2f);
const ImVec2 touch_bar_pos = keyNumpadItemSize * ImVec2(0.125f, 0.6f);
@@ -1018,7 +1025,7 @@ void InputMappingWindow::Render()
}
ImGui::PopStyleColor(2);
ImGui::PopStyleColor(3);
ImGui::PopStyleVar(2);
ImGui::PopFont();
@@ -1055,6 +1062,7 @@ void InputMappingWindow::Render()
color = ImGui::GetStyle().Colors[ImGuiCol_Text];
color.w /= Settings::application.mapping.disabled ? 2.f : 1.0f;
ImGui::PushStyleColor(ImGuiCol_Text, color);
ImGui::PushStyleColor(ImGuiCol_Header, ImGui::GetColorU32(ImGuiCol_Header, 0.4f));
// CENTER text for button
ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, ImVec2(0.5f, 0.5f));
@@ -1184,8 +1192,8 @@ void InputMappingWindow::Render()
ImGui::PopStyleVar(2);
// Done with color and font change
ImGui::PopStyleColor(2);
// Done with color change
ImGui::PopStyleColor(3);
}
//
@@ -1199,37 +1207,43 @@ void InputMappingWindow::Render()
const glm::vec2 mpo = glm::vec2 (io.MousePos.x - circle_center.x, io.MousePos.y - circle_center.y);
const float angle = - glm::orientedAngle( glm::normalize(mpo), glm::vec2(1.f,0.f));
const float lenght = glm::length(mpo);
const float cm = 0.02f ; // circle margin
const float cm = 0.03f ; // circle margin
// color palette
static ImU32 colorbg = ImGui::GetColorU32(ImGuiCol_FrameBgActive, 0.6f);
static ImU32 colorfg = ImGui::GetColorU32(ImGuiCol_FrameBg, 2.5f);
static ImU32 colorover = ImGui::GetColorU32(ImGuiCol_Header);
ImVec4 color = ImGui::GetStyle().Colors[ImGuiCol_Header];
color.w /= Settings::application.mapping.disabled ? 2.f : 0.9f;
ImGui::PushStyleColor(ImGuiCol_Header, color);
color = ImGui::GetStyle().Colors[ImGuiCol_Text];
color.w /= Settings::application.mapping.disabled ? 2.f : 1.0f;
ImGui::PushStyleColor(ImGuiCol_Text, color);
ImGui::PushStyleColor(ImGuiCol_Header, ImGui::GetColorU32(ImGuiCol_Header, 0.4f));
// draw background ring
static ImU32 colorbg = ImGui::GetColorU32(ImGuiCol_FrameBgActive, 0.6f);
draw_list->AddCircleFilled(circle_center, circle_radius, colorbg, PLOT_CIRCLE_SEGMENTS);
// draw slices
// metronome timer info
char text_buf[24] = {0};
const double q = Metronome::manager().quantum();
static const float resolution = PLOT_CIRCLE_SEGMENTS / (2.f * M_PI);
static ImVec2 buffer[PLOT_CIRCLE_SEGMENTS];
for (int p = 0.0 ; p < (int) floor(q) ; p++) {
float a0 = cm + - M_PI_2 + (float(p)/floor(q)) * (2.f * M_PI);
float a1 = (-2.f * 0.02) + a0 + (1.f / floor(q)) * (2.f * M_PI);
// draw all slices of metronome ring
for (uint ip = 0 ; ip < (uint) floor(q) ; ++ip) {
float a0 = cm - M_PI_2 + (float(ip) * (2.f * M_PI)) / floor(q);
float a1 = (-2.f * cm) + a0 + (2.f * M_PI) / floor(q);
int n = ImMax(3, (int)((a1 - a0) * resolution));
double da = (a1 - a0) / (n - 1);
int index = 0;
float a01 = -cm + a0 + (0.5f / floor(q)) * (2.f * M_PI);
buffer[index++] = circle_center + ImVec2(circle_radius * cm * cos(a01), circle_radius * cm * sin(a01));
// start drawing at center point of slice
float a01 = (a0 + a1) / 2.f;
buffer[index++] = ImVec2(circle_center.x + cm * circle_radius * cos(a01), circle_center.y + cm * circle_radius * sin(a01));
// draw round external border of slice
for (int i = 0; i < n; ++i) {
double a = a0 + i * da;
buffer[index++] = ImVec2(circle_center.x + circle_radius * cos(a), circle_center.y + circle_radius * sin(a));
}
// test mouse over in slices of the circle
// Test mouse over in slices of the circle
// 1) test if mouse is inside area
if (ImGui::IsMouseHoveringRect(frame_top, frame_top + ImVec2(inputarea_width, inputarea_width), true))
{
@@ -1237,30 +1251,45 @@ void InputMappingWindow::Render()
if (lenght < circle_radius && ( ( angle > a0 && angle < a1) || (angle + (2.f * M_PI) > a0 && angle + (2.f * M_PI) < a1) ) )
{
// draw the mouse-over slice
draw_list->AddConvexPolyFilled(buffer, index, colorover);
draw_list->AddConvexPolyFilled(buffer, index, ImGui::GetColorU32(ImGuiCol_HeaderHovered));
// indicate tempo of mouse-over slice
snprintf(text_buf, 24, "%d/%d", p + 1, (int) floor(q) );
snprintf(text_buf, 24, "%d/%d", ip + 1, (int) floor(q) );
// mouse clic
if (ImGui::IsMouseClicked(0)) {
current_input_ = ip + INPUT_TIMER_FIRST;
}
}
}
float border = 1.f;
// TODO : currently edited slice has a large border
// draw the slice showing its assigned in this session
if (S->inputAssigned(ip + INPUT_TIMER_FIRST))
draw_list->AddConvexPolyFilled(buffer, index, ImGui::GetColorU32(ImGuiCol_Header));
// draw the border of the slice
draw_list->AddPolyline(buffer, index, colorfg, true, border);
if (ip + INPUT_TIMER_FIRST == current_input_)
// current active slice has bold white border
draw_list->AddPolyline(buffer, index, ImGui::GetColorU32(ImGuiCol_Text), true, 3.f);
else
draw_list->AddPolyline(buffer, index, ImGui::GetColorU32(ImGuiCol_Button), true, 0.5f);
}
// centered indicator 'x / N' on mouse over
draw_list->AddCircleFilled(circle_center, circle_radius * 0.25f, colorfg, PLOT_CIRCLE_SEGMENTS);
// draw clock hand
float a = -M_PI_2 + (Metronome::manager().phase()/q) * (2.f * M_PI);
draw_list->AddLine(ImVec2(circle_center.x + cos(a), circle_center.y + sin(a)),
ImVec2(circle_center.x + circle_radius * cos(a), circle_center.y + circle_radius * sin(a)),
ImGui::GetColorU32(ImGuiCol_PlotHistogram), 2.f);
// display text indication in the center
// display text indication 'x / N' on mouse over in a central circle
draw_list->AddCircleFilled(circle_center, circle_radius * 0.25f, ImGui::GetColorU32(ImGuiCol_Button, 10.f), PLOT_CIRCLE_SEGMENTS);
ImGuiToolkit::PushFont(ImGuiToolkit::FONT_MONO);
ImVec2 label_size = ImGui::CalcTextSize(text_buf, NULL);
ImGui::SetCursorScreenPos(circle_center - label_size/2);
ImGui::Text("%s", text_buf);
ImGui::PopFont();
ImGui::PopStyleColor(3);
}
// Draw child Window (right) to list reactions to input

View File

@@ -1594,8 +1594,9 @@ void SourceControlWindow::RenderSelectedSources()
{
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.f, 2.f * v_space_));
// area horizontal pack
int numcolumns = CLAMP( int(ceil(1.0f * rendersize.x / rendersize.y)), 1, numsources );
// area horizontal
rendersize.y -= buttons_height_ + 2.f * v_space_;
int numcolumns = CLAMP( int(ceil(rendersize.x / rendersize.y)), 1, numsources );
ImGui::Columns( numcolumns, "##selectiongrid", false);
float widthcolumn = rendersize.x / static_cast<float>(numcolumns);
widthcolumn -= scrollbar_;

View File

@@ -901,7 +901,7 @@ void UserInterface::NewFrame()
}
}
// overlay to ensure file color dialog is closed after use
// overlay to ensure color dialog is closed after use
if (DialogToolkit::ColorPickerDialog::instance().busy()){
if (!ImGui::IsPopupOpen("##ColorBusy"))
ImGui::OpenPopup("##ColorBusy");