Skip to content

Commit 71fa653

Browse files
committed
Implement volume button hold/repeat with delay
Improve the button handling code so that a pressed and held button will now generate additional press events after a short delay. An acceleration curve is also applied so that events are generated with increasing frequency the longer the button is held down. Closes #323.
1 parent 9de98b5 commit 71fa653

File tree

7 files changed

+78
-6
lines changed

7 files changed

+78
-6
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2020
- Update to libmt32emu v2.7.0.
2121
- Update to FluidSynth v2.3.1.
2222
- Update to inih r56.
23+
- Volume up/down buttons in the `simple_buttons` control scheme now repeat and accelerate when pressed and held (issue #323).
2324
- Updater: `config.txt` is now replaced on update, however the `avoid_warnings` setting is preserved (the only setting that matters to most users).
2425
- Updater: improved error handling.
2526
- Installer: less often-selected Wi-Fi countries have been moved to a separate list.

include/control/button.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,18 @@
2323
#ifndef _button_h
2424
#define _button_h
2525

26-
enum class TButton
26+
#include <circle/types.h>
27+
28+
enum TButton : u8
2729
{
2830
Button1,
2931
Button2,
3032
Button3,
3133
Button4,
3234

33-
EncoderButton
35+
EncoderButton,
36+
37+
Max,
3438
};
3539

3640
#endif

include/control/control.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
#include "control/rotaryencoder.h"
3131
#include "event.h"
32+
#include "optional.h"
3233
#include "utility.h"
3334

3435
class CControl
@@ -51,15 +52,39 @@ class CControl
5152
static constexpr size_t ButtonStateHistoryLength = 16;
5253
static constexpr size_t ButtonStateHistoryMask = ButtonStateHistoryLength - 1;
5354

55+
static constexpr u32 RepeatDelayMicros = 500000; // 500ms
56+
static constexpr u32 RepeatAccelTimeMicros = 3000000; // 3s
57+
static constexpr u32 MaxRepeatPeriodMicros = 100000; // 10Hz
58+
static constexpr u32 MinRepeatPeriodMicros = 20000; // 50Hz
59+
5460
TEventQueue* m_pEventQueue;
5561
CUserTimer m_Timer;
5662

5763
// Debouncing
5864
u8 m_ButtonStateHistory[ButtonStateHistoryLength];
5965
size_t m_nButtonStateHistoryIndex;
66+
67+
// State tracking
6068
u8 m_nButtonState;
6169
u8 m_nLastButtonState;
6270

71+
// Repeat
72+
TOptional<u8> m_RepeatButton;
73+
u32 m_PressedTime;
74+
u32 m_RepeatTime;
75+
76+
static inline u32 RepeatPeriod(u32 nPressedDuration)
77+
{
78+
return Utility::Lerp
79+
(
80+
nPressedDuration,
81+
0,
82+
RepeatAccelTimeMicros,
83+
MaxRepeatPeriodMicros,
84+
MinRepeatPeriodMicros
85+
);
86+
}
87+
6388
static void InterruptHandler(CUserTimer* pUserTimer, void* pParam);
6489
};
6590

include/event.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ struct TButtonEvent
3333
{
3434
TButton Button;
3535
bool bPressed;
36+
bool bRepeat;
3637
};
3738

3839
struct TEncoderEvent

include/utility.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ namespace Utility
6161
return nLHS > nRHS ? nLHS : nRHS;
6262
}
6363

64+
// Function for performing a linear interpolation of a value
65+
constexpr float Lerp(float nValue, float nMinA, float nMaxA, float nMinB, float nMaxB)
66+
{
67+
return nMinB + (nValue - nMinA) * ((nMaxB - nMinB) / (nMaxA - nMinA));
68+
}
69+
6470
// Return number of elements in an array
6571
template <class T, size_t N>
6672
constexpr size_t ArraySize(const T(&)[N]) { return N; }

src/control/control.cpp

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
//
2222

2323
#include <circle/interrupt.h>
24+
#include <circle/timer.h>
2425

2526
#include "control/control.h"
2627

@@ -33,7 +34,10 @@ CControl::CControl(TEventQueue& pEventQueue)
3334
m_ButtonStateHistory{0},
3435
m_nButtonStateHistoryIndex(0),
3536
m_nButtonState(0),
36-
m_nLastButtonState(0)
37+
m_nLastButtonState(0),
38+
m_RepeatButton(),
39+
m_PressedTime(0),
40+
m_RepeatTime(0)
3741
{
3842
}
3943

@@ -55,21 +59,52 @@ void CControl::Update()
5559
{
5660
Event.Type = TEventType::Button;
5761

58-
for (u8 i = 0; i < 8; ++i)
62+
for (u8 i = 0; i < TButton::Max; ++i)
5963
{
6064
const bool bCurrentState = m_nButtonState & (1 << i);
6165
const bool bLastState = m_nLastButtonState & (1 << i);
6266

6367
if (bCurrentState != bLastState)
6468
{
69+
if (bCurrentState)
70+
{
71+
m_RepeatButton = i;
72+
m_PressedTime = CTimer::GetClockTicks();
73+
m_RepeatTime = 0;
74+
}
75+
else if (m_RepeatButton && m_RepeatButton.Value() == i)
76+
m_RepeatButton.Reset();
77+
6578
Event.Button.Button = static_cast<TButton>(i);
6679
Event.Button.bPressed = bCurrentState;
80+
Event.Button.bRepeat = false;
6781
m_pEventQueue->Enqueue(Event);
6882
}
6983
}
7084

7185
m_nLastButtonState = m_nButtonState;
7286
}
87+
88+
// Handle repeat
89+
if (m_RepeatButton)
90+
{
91+
const u32 nTicks = CTimer::GetClockTicks();
92+
const u32 nPressedDuration = nTicks - m_PressedTime;
93+
94+
if (nPressedDuration > RepeatDelayMicros)
95+
{
96+
if (m_RepeatTime == 0)
97+
m_RepeatTime = nTicks;
98+
else if (nTicks - m_RepeatTime > RepeatPeriod(nPressedDuration - RepeatDelayMicros))
99+
{
100+
Event.Button.Button = static_cast<TButton>(m_RepeatButton.Value());
101+
Event.Button.bPressed = true;
102+
Event.Button.bRepeat = true;
103+
m_pEventQueue->Enqueue(Event);
104+
m_RepeatTime = nTicks;
105+
}
106+
}
107+
}
73108
}
74109

75110
void CControl::DebounceButtonState(u8 nState, u8 nMask)

src/mt32pi.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,15 +1068,15 @@ void CMT32Pi::ProcessButtonEvent(const TButtonEvent& Event)
10681068
if (!Event.bPressed)
10691069
return;
10701070

1071-
if (Event.Button == TButton::Button1)
1071+
if (Event.Button == TButton::Button1 && !Event.bRepeat)
10721072
{
10731073
// Swap synths
10741074
if (m_pCurrentSynth == m_pMT32Synth)
10751075
SwitchSynth(TSynth::SoundFont);
10761076
else
10771077
SwitchSynth(TSynth::MT32);
10781078
}
1079-
else if (Event.Button == TButton::Button2 && Event.bPressed)
1079+
else if (Event.Button == TButton::Button2 && !Event.bRepeat)
10801080
{
10811081
if (m_pCurrentSynth == m_pMT32Synth)
10821082
NextMT32ROMSet();

0 commit comments

Comments
 (0)