//
// uibuttons.cpp
//
// MiniDexed - Dexed FM synthesizer for bare metal Raspberry Pi
// Copyright (C) 2022 The MiniDexed Team
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
//
#include "uibuttons.h"
#include
#include
#include
#include
LOGMODULE ("uibuttons");
CUIButton::CUIButton (void)
: m_pinNumber (0),
m_pin (0),
m_midipin (0),
m_lastValue (1),
m_timer (0),
m_debounceTimer (0),
m_numClicks (0),
m_clickEvent(BtnEventNone),
m_doubleClickEvent(BtnEventNone),
m_longPressEvent(BtnEventNone),
m_doubleClickTimeout(0),
m_longPressTimeout(0)
{
}
CUIButton::~CUIButton (void)
{
if (m_pin)
{
delete m_pin;
}
if (m_midipin)
{
delete m_midipin;
}
}
void CUIButton::reset (void)
{
m_timer = m_longPressTimeout;
m_numClicks = 0;
}
boolean CUIButton::Initialize (unsigned pinNumber, unsigned doubleClickTimeout, unsigned longPressTimeout)
{
assert (!m_pin);
assert(longPressTimeout >= doubleClickTimeout);
m_pinNumber = pinNumber;
m_doubleClickTimeout = doubleClickTimeout;
m_longPressTimeout = longPressTimeout;
// Initialise timing values
m_timer = m_longPressTimeout;
m_debounceTimer = DEBOUNCE_TIME;
if (m_pinNumber != 0)
{
if (isMidiPin(m_pinNumber))
{
LOGDBG("MIDI Button on pin: %d (%x)", m_pinNumber, m_pinNumber);
m_midipin = new CMIDIPin (m_pinNumber);
} else {
LOGDBG("GPIO Button on pin: %d (%x)", m_pinNumber, m_pinNumber);
m_pin = new CGPIOPin (m_pinNumber, GPIOModeInputPullUp);
}
}
return TRUE;
}
void CUIButton::setClickEvent(BtnEvent clickEvent)
{
m_clickEvent = clickEvent;
}
void CUIButton::setDoubleClickEvent(BtnEvent doubleClickEvent)
{
m_doubleClickEvent = doubleClickEvent;
}
void CUIButton::setLongPressEvent(BtnEvent longPressEvent)
{
m_longPressEvent = longPressEvent;
}
unsigned CUIButton::getPinNumber(void)
{
return m_pinNumber;
}
CUIButton::BtnTrigger CUIButton::ReadTrigger (void)
{
unsigned value;
if (isMidiPin(m_pinNumber))
{
if (!m_midipin)
{
// Always return "not pressed" if not configured
return BtnTriggerNone;
}
value = m_midipin->Read();
}
else
{
if (!m_pin)
{
// Always return "not pressed" if not configured
return BtnTriggerNone;
}
value = m_pin->Read();
}
if (m_timer < m_longPressTimeout) {
m_timer++;
if (m_timer == m_doubleClickTimeout && m_lastValue == 1 && m_numClicks == 1) {
// The user has clicked and released the button once within the
// timeout - this must be a single click
reset();
return BtnTriggerClick;
}
if (m_timer == m_longPressTimeout) {
if (m_lastValue == 0 && m_numClicks == 1) {
// Single long press
reset();
return BtnTriggerLongPress;
}
else {
// Just reset it - we've run out of possible interactions
reset();
}
}
}
// Debounce here - we don't need to do anything if the debounce timer is active
if (m_debounceTimer < DEBOUNCE_TIME) {
m_debounceTimer++;
return BtnTriggerNone;
}
// Buttons in PULL UP mode are "active low"
if (value == 0)
{
if (m_lastValue == 0) {
// 0 -> 0 : Button is pressed, was already pressed
}
else {
// 1 -> 0 : Button was not pressed but is now pressed
m_lastValue = 0;
m_debounceTimer = 0;
if (m_numClicks == 0) {
// No clicks recorded - start a new timer
m_timer = 0;
}
if (m_numClicks < 2) {
m_numClicks++;
}
}
}
else
{
if (m_lastValue == 1) {
// 1 -> 1 : Button is not pressed, was already not pressed
}
else {
// 0 -> 1 : Button was pressed but is now not pressed (it was released)
m_lastValue = 1;
m_debounceTimer = 0;
if (m_numClicks == 1 &&
(m_doubleClickEvent == BtnEventNone ||
m_timer >= m_doubleClickTimeout && m_timer < m_longPressTimeout)
) {
// Either the user released the button when there is no double
// click mapped
// OR:
// The user released the button after the double click
// timeout, but before the long press timeout
reset();
return BtnTriggerClick;
}
else if (m_numClicks == 2) {
// This is the second release in a short period of time
reset();
return BtnTriggerDoubleClick;
}
}
}
return BtnTriggerNone;
}
void CUIButton::Write (unsigned nValue) {
// This only makes sense for MIDI buttons.
if (m_midipin && isMidiPin(m_pinNumber))
{
// Update the "MIDI Pin"
m_midipin->Write(nValue);
}
}
CUIButton::BtnEvent CUIButton::Read (void) {
BtnTrigger trigger = ReadTrigger();
if (trigger == BtnTriggerClick) {
return m_clickEvent;
}
else if (trigger == BtnTriggerDoubleClick) {
return m_doubleClickEvent;
}
else if (trigger == BtnTriggerLongPress) {
return m_longPressEvent;
}
assert (trigger == BtnTriggerNone);
return BtnEventNone;
}
CUIButton::BtnTrigger CUIButton::triggerTypeFromString(const char* triggerString)
{
if (strcmp(triggerString, "") == 0 || strcmp(triggerString, "none") == 0) {
return BtnTriggerNone;
}
else if (strcmp(triggerString, "click") == 0) {
return BtnTriggerClick;
}
else if (strcmp(triggerString, "doubleclick") == 0) {
return BtnTriggerDoubleClick;
}
else if (strcmp(triggerString, "longpress") == 0) {
return BtnTriggerLongPress;
}
LOGERR("Invalid action: %s", triggerString);
return BtnTriggerNone;
}
CUIButtons::CUIButtons (
unsigned prevPin, const char *prevAction,
unsigned nextPin, const char *nextAction,
unsigned backPin, const char *backAction,
unsigned selectPin, const char *selectAction,
unsigned homePin, const char *homeAction,
unsigned doubleClickTimeout, unsigned longPressTimeout,
unsigned prevMidi, unsigned nextMidi, unsigned backMidi, unsigned selectMidi, unsigned homeMidi
)
: m_doubleClickTimeout(doubleClickTimeout),
m_longPressTimeout(longPressTimeout),
m_prevPin(prevPin),
m_prevAction(CUIButton::triggerTypeFromString(prevAction)),
m_nextPin(nextPin),
m_nextAction(CUIButton::triggerTypeFromString(nextAction)),
m_backPin(backPin),
m_backAction(CUIButton::triggerTypeFromString(backAction)),
m_selectPin(selectPin),
m_selectAction(CUIButton::triggerTypeFromString(selectAction)),
m_homePin(homePin),
m_homeAction(CUIButton::triggerTypeFromString(homeAction)),
m_prevMidi(ccToMidiPin(prevMidi)),
m_nextMidi(ccToMidiPin(nextMidi)),
m_backMidi(ccToMidiPin(backMidi)),
m_selectMidi(ccToMidiPin(selectMidi)),
m_homeMidi(ccToMidiPin(homeMidi)),
m_eventHandler (0),
m_lastTick (0)
{
}
CUIButtons::~CUIButtons (void)
{
}
boolean CUIButtons::Initialize (void)
{
// First sanity check and convert the timeouts:
// Internally values are in tenths of a millisecond, but config values
// are in milliseconds
unsigned doubleClickTimeout = m_doubleClickTimeout * 10;
unsigned longPressTimeout = m_longPressTimeout * 10;
if (longPressTimeout < doubleClickTimeout) {
// This is invalid - long press must be longest timeout
LOGERR("LongPressTimeout (%u) should not be shorter than DoubleClickTimeout (%u)",
m_longPressTimeout,
m_doubleClickTimeout);
// Just make long press as long as double click
longPressTimeout = doubleClickTimeout;
}
// Each normal button can be assigned up to 3 actions: click, doubleclick and
// longpress. We may not initialise all of the buttons.
// MIDI buttons only support a single click.
unsigned pins[MAX_BUTTONS] = {
m_prevPin, m_nextPin, m_backPin, m_selectPin, m_homePin,
m_prevMidi, m_nextMidi, m_backMidi, m_selectMidi, m_homeMidi
};
CUIButton::BtnTrigger triggers[MAX_BUTTONS] = {
// Normal buttons
m_prevAction, m_nextAction, m_backAction, m_selectAction, m_homeAction,
// MIDI Buttons only support a single click (at present)
CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick, CUIButton::BtnTriggerClick
};
CUIButton::BtnEvent events[MAX_BUTTONS] = {
// Normal buttons
CUIButton::BtnEventPrev,
CUIButton::BtnEventNext,
CUIButton::BtnEventBack,
CUIButton::BtnEventSelect,
CUIButton::BtnEventHome,
// MIDI buttons
CUIButton::BtnEventPrev,
CUIButton::BtnEventNext,
CUIButton::BtnEventBack,
CUIButton::BtnEventSelect,
CUIButton::BtnEventHome
};
// Setup normal GPIO buttons first
for (unsigned i=0; i