Merge branch 'main' of https://git.pinkduck.xyz/adam/libGravity into euclidean

This commit is contained in:
2025-06-28 09:48:40 -07:00
6 changed files with 33 additions and 57 deletions

View File

@ -16,28 +16,21 @@
#include "button.h" #include "button.h"
#include "peripherials.h" #include "peripherials.h"
enum Direction { class Encoder {
DIRECTION_UNCHANGED,
DIRECTION_INCREMENT,
DIRECTION_DECREMENT,
};
class EncoderDir {
protected: protected:
typedef void (*CallbackFunction)(void); typedef void (*CallbackFunction)(void);
typedef void (*RotateCallbackFunction)(Direction dir, int val); typedef void (*RotateCallbackFunction)(int val);
CallbackFunction on_press; CallbackFunction on_press;
RotateCallbackFunction on_press_rotate; RotateCallbackFunction on_press_rotate;
RotateCallbackFunction on_rotate; RotateCallbackFunction on_rotate;
int change; int change;
Direction dir;
public: public:
EncoderDir() : encoder_(ENCODER_PIN1, ENCODER_PIN2, RotaryEncoder::LatchMode::FOUR3), Encoder() : encoder_(ENCODER_PIN1, ENCODER_PIN2, RotaryEncoder::LatchMode::FOUR3),
button_(ENCODER_SW_PIN) { button_(ENCODER_SW_PIN) {
_instance = this; _instance = this;
} }
~EncoderDir() {} ~Encoder() {}
// Set to true if the encoder read direction should be reversed. // Set to true if the encoder read direction should be reversed.
void SetReverseDirection(bool reversed) { void SetReverseDirection(bool reversed) {
@ -55,12 +48,6 @@ class EncoderDir {
on_press_rotate = f; on_press_rotate = f;
} }
// Parse EncoderButton increment direction.
Direction RotateDirection() {
int dir = (int)(encoder_.getDirection());
return rotate_(dir, reversed_);
}
void Process() { void Process() {
// Get encoder position change amount. // Get encoder position change amount.
int encoder_rotated = _rotate_change() != 0; int encoder_rotated = _rotate_change() != 0;
@ -70,9 +57,9 @@ class EncoderDir {
// Handle encoder position change and button press. // Handle encoder position change and button press.
if (button_pressed && encoder_rotated) { if (button_pressed && encoder_rotated) {
rotated_while_held_ = true; rotated_while_held_ = true;
if (on_press_rotate != NULL) on_press_rotate(dir, change); if (on_press_rotate != NULL) on_press_rotate(change);
} else if (!button_pressed && encoder_rotated) { } else if (!button_pressed && encoder_rotated) {
if (on_rotate != NULL) on_rotate(dir, change); if (on_rotate != NULL) on_rotate(change);
} else if (button_.Change() == Button::CHANGE_RELEASED && !rotated_while_held_) { } else if (button_.Change() == Button::CHANGE_RELEASED && !rotated_while_held_) {
if (on_press != NULL) on_press(); if (on_press != NULL) on_press();
} }
@ -91,7 +78,7 @@ class EncoderDir {
} }
private: private:
static EncoderDir* _instance; static Encoder* _instance;
int previous_pos_; int previous_pos_;
bool rotated_while_held_; bool rotated_while_held_;
@ -112,7 +99,6 @@ class EncoderDir {
// Update state variables. // Update state variables.
change = position - previous_pos_; change = position - previous_pos_;
previous_pos_ = position; previous_pos_ = position;
dir = RotateDirection();
// Encoder rotate acceleration. // Encoder rotate acceleration.
if (ms < 16) { if (ms < 16) {
@ -126,17 +112,6 @@ class EncoderDir {
} }
return change; return change;
} }
inline Direction rotate_(int dir, bool reversed) {
switch (dir) {
case 1:
return (reversed) ? DIRECTION_DECREMENT : DIRECTION_INCREMENT;
case -1:
return (reversed) ? DIRECTION_INCREMENT : DIRECTION_DECREMENT;
default:
return DIRECTION_UNCHANGED;
}
}
}; };
#endif #endif

View File

@ -142,7 +142,7 @@ void HandleEncoderPressed() {
app.refresh_screen = true; app.refresh_screen = true;
} }
void HandleRotate(Direction dir, int val) { void HandleRotate(int val) {
if (!app.editing_param) { if (!app.editing_param) {
// Navigation Mode // Navigation Mode
const int max_param = (app.selected_channel == 0) ? PARAM_MAIN_LAST : PARAM_CH_LAST; const int max_param = (app.selected_channel == 0) ? PARAM_MAIN_LAST : PARAM_CH_LAST;
@ -158,12 +158,8 @@ void HandleRotate(Direction dir, int val) {
app.refresh_screen = true; app.refresh_screen = true;
} }
void HandlePressedRotate(Direction dir, int val) { void HandlePressedRotate(int val) {
if (dir == DIRECTION_INCREMENT && app.selected_channel < Gravity::OUTPUT_COUNT) { updateSelection(app.selected_channel, val, Gravity::OUTPUT_COUNT + 1);
app.selected_channel++;
} else if (dir == DIRECTION_DECREMENT && app.selected_channel > 0) {
app.selected_channel--;
}
app.selected_param = 0; app.selected_param = 0;
stateManager.markDirty(); stateManager.markDirty();
app.refresh_screen = true; app.refresh_screen = true;
@ -180,7 +176,7 @@ void editMainParameter(int val) {
break; break;
case PARAM_MAIN_SOURCE: { case PARAM_MAIN_SOURCE: {
int source = static_cast<int>(app.selected_source); byte source = static_cast<int>(app.selected_source);
updateSelection(source, val, Clock::SOURCE_LAST); updateSelection(source, val, Clock::SOURCE_LAST);
app.selected_source = static_cast<Clock::Source>(source); app.selected_source = static_cast<Clock::Source>(source);
gravity.clock.SetSource(app.selected_source); gravity.clock.SetSource(app.selected_source);
@ -220,13 +216,13 @@ void editChannelParameter(int val) {
ch.setHits(ch.getHits() + val); ch.setHits(ch.getHits() + val);
break; break;
case PARAM_CH_CV_SRC: { case PARAM_CH_CV_SRC: {
int source = static_cast<int>(ch.getCvSource()); byte source = static_cast<int>(ch.getCvSource());
updateSelection(source, val, CV_LAST); updateSelection(source, val, CV_LAST);
ch.setCvSource(static_cast<CvSource>(source)); ch.setCvSource(static_cast<CvSource>(source));
break; break;
} }
case PARAM_CH_CV_DEST: { case PARAM_CH_CV_DEST: {
int dest = static_cast<int>(ch.getCvDestination()); byte dest = static_cast<int>(ch.getCvDestination());
updateSelection(dest, val, CV_DEST_LAST); updateSelection(dest, val, CV_DEST_LAST);
ch.setCvDestination(static_cast<CvDestination>(dest)); ch.setCvDestination(static_cast<CvDestination>(dest));
break; break;
@ -234,12 +230,17 @@ void editChannelParameter(int val) {
} }
} }
void updateSelection(int& param, int change, int maxValue) { // Changes the param by the value provided.
void updateSelection(byte& param, int change, int maxValue) {
// Do not apply acceleration if max value is less than 25.
if (maxValue < 25) {
change = change > 0 ? 1 : -1;
}
param = constrain(param + change, 0, maxValue - 1); param = constrain(param + change, 0, maxValue - 1);
} }
// //
// Helper functions. // App Helper functions.
// //
void InitAppState(AppState& app) { void InitAppState(AppState& app) {

View File

@ -11,8 +11,8 @@ struct AppState {
bool encoder_reversed = false; bool encoder_reversed = false;
bool refresh_screen = true; bool refresh_screen = true;
bool editing_param = false; bool editing_param = false;
int selected_param = 0; byte selected_param = 0;
int selected_sub_param = 0; byte selected_sub_param = 0;
byte selected_channel = 0; // 0=tempo, 1-6=output channel byte selected_channel = 0; // 0=tempo, 1-6=output channel
byte selected_shuffle = 0; byte selected_shuffle = 0;
Clock::Source selected_source = Clock::SOURCE_INTERNAL; Clock::Source selected_source = Clock::SOURCE_INTERNAL;

View File

@ -9,7 +9,7 @@
// UI Display functions for drawing the UI to the OLED display. // UI Display functions for drawing the UI to the OLED display.
// //
const PROGMEM uint8_t TEXT_FONT[437] U8G2_FONT_SECTION("velvetscreen") = const uint8_t TEXT_FONT[437] U8G2_FONT_SECTION("velvetscreen") PROGMEM =
"\64\0\2\2\3\3\2\3\4\5\5\0\0\5\0\5\0\0\221\0\0\1\230 \4\200\134%\11\255tT" "\64\0\2\2\3\3\2\3\4\5\5\0\0\5\0\5\0\0\221\0\0\1\230 \4\200\134%\11\255tT"
"R\271RI(\6\252\334T\31)\7\252\134bJ\12+\7\233\345\322J\0,\5\221T\4-\5\213" "R\271RI(\6\252\334T\31)\7\252\134bJ\12+\7\233\345\322J\0,\5\221T\4-\5\213"
"f\6.\5\211T\2/\6\244\354c\33\60\10\254\354T\64\223\2\61\7\353\354\222\254\6\62\11\254l" "f\6.\5\211T\2/\6\244\354c\33\60\10\254\354T\64\223\2\61\7\353\354\222\254\6\62\11\254l"
@ -25,7 +25,7 @@ const PROGMEM uint8_t TEXT_FONT[437] U8G2_FONT_SECTION("velvetscreen") =
"\7p\10\255\364V\266\323\2q\7\255\364\216\257\5r\10\253d\242\32*\2t\6\255t\376#w\11" "\7p\10\255\364V\266\323\2q\7\255\364\216\257\5r\10\253d\242\32*\2t\6\255t\376#w\11"
"\255\364V\245FN\13x\6\233dR\7\0\0\0\4\377\377\0"; "\255\364V\245FN\13x\6\233dR\7\0\0\0\4\377\377\0";
const PROGMEM uint8_t LARGE_FONT[916] U8G2_FONT_SECTION("stk-l") = const uint8_t LARGE_FONT[916] U8G2_FONT_SECTION("stk-l") PROGMEM =
"#\0\4\4\4\5\2\1\6\17\30\1\0\27\0\0\0\1\77\0\0\3w%'\17\37\313\330R#&" "#\0\4\4\4\5\2\1\6\17\30\1\0\27\0\0\0\1\77\0\0\3w%'\17\37\313\330R#&"
"\32!F\14\211I\310\24!\65\204(MF\21)Cd\304\10\62b\14\215\60Vb\334\20\0/\14" "\32!F\14\211I\310\24!\65\204(MF\21)Cd\304\10\62b\14\215\60Vb\334\20\0/\14"
"\272\336\336d\244\350\263q\343\0\60\37|\377\216!%*\10\35\263\253ChD\30\21bB\14\242S" "\272\336\336d\244\350\263q\343\0\60\37|\377\216!%*\10\35\263\253ChD\30\21bB\14\242S"
@ -58,11 +58,11 @@ const PROGMEM uint8_t LARGE_FONT[916] U8G2_FONT_SECTION("stk-l") =
#define play_icon_width 14 #define play_icon_width 14
#define play_icon_height 14 #define play_icon_height 14
static const unsigned char play_icon[] PROGMEM = { static const unsigned char play_icon[28] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x7C, 0x00, 0xFC, 0x00, 0xFC, 0x03, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x7C, 0x00, 0xFC, 0x00, 0xFC, 0x03,
0xFC, 0x0F, 0xFC, 0x0F, 0xFC, 0x03, 0xFC, 0x00, 0x7C, 0x00, 0x3C, 0x00, 0xFC, 0x0F, 0xFC, 0x0F, 0xFC, 0x03, 0xFC, 0x00, 0x7C, 0x00, 0x3C, 0x00,
0x00, 0x00, 0x00, 0x00}; 0x00, 0x00, 0x00, 0x00};
static const unsigned char pause_icon[] PROGMEM = { static const unsigned char pause_icon[28] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E,
0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E, 0x38, 0x0E,
0x38, 0x0E, 0x00, 0x00}; 0x38, 0x0E, 0x00, 0x00};
@ -278,8 +278,8 @@ void DisplayChannelPage() {
subText = "EUCLID HITS"; subText = "EUCLID HITS";
break; break;
case PARAM_CH_CV_SRC: { case PARAM_CH_CV_SRC: {
mainText = F("SRC");
switch (ch.getCvSource()) { switch (ch.getCvSource()) {
mainText = F("SRC");
case CV_NONE: case CV_NONE:
subText = F("NONE"); subText = F("NONE");
break; break;
@ -293,8 +293,8 @@ void DisplayChannelPage() {
break; break;
} }
case PARAM_CH_CV_DEST: { case PARAM_CH_CV_DEST: {
mainText = F("DEST");
switch (ch.getCvDestination()) { switch (ch.getCvDestination()) {
mainText = F("DEST");
case CV_DEST_NONE: case CV_DEST_NONE:
subText = F("NONE"); subText = F("NONE");
break; break;

View File

@ -13,7 +13,7 @@
// Initialize the static pointer for the EncoderDir class to null. We want to // Initialize the static pointer for the EncoderDir class to null. We want to
// have a static pointer to decouple the ISR from the global gravity object. // have a static pointer to decouple the ISR from the global gravity object.
EncoderDir* EncoderDir::_instance = nullptr; Encoder* Encoder::_instance = nullptr;
void Gravity::Init() { void Gravity::Init() {
initClock(); initClock();
@ -74,11 +74,11 @@ void Gravity::Process() {
// Pin Change Interrupt on Port D (D4). // Pin Change Interrupt on Port D (D4).
ISR(PCINT2_vect) { ISR(PCINT2_vect) {
EncoderDir::isr(); Encoder::isr();
}; };
// Pin Change Interrupt on Port C (D17/A3). // Pin Change Interrupt on Port C (D17/A3).
ISR(PCINT1_vect) { ISR(PCINT1_vect) {
EncoderDir::isr(); Encoder::isr();
}; };
// Global instance // Global instance

View File

@ -8,7 +8,7 @@
#include "button.h" #include "button.h"
#include "clock.h" #include "clock.h"
#include "digital_output.h" #include "digital_output.h"
#include "encoder_dir.h" #include "encoder.h"
#include "peripherials.h" #include "peripherials.h"
// Hardware abstraction wrapper for the Gravity module. // Hardware abstraction wrapper for the Gravity module.
@ -32,7 +32,7 @@ class Gravity {
U8G2_SSD1306_128X64_NONAME_1_HW_I2C display; // OLED display object. U8G2_SSD1306_128X64_NONAME_1_HW_I2C display; // OLED display object.
Clock clock; // Clock source wrapper. Clock clock; // Clock source wrapper.
DigitalOutput outputs[OUTPUT_COUNT]; // An array containing each Output object. DigitalOutput outputs[OUTPUT_COUNT]; // An array containing each Output object.
EncoderDir encoder; // Rotary encoder with button instance Encoder encoder; // Rotary encoder with button instance
Button shift_button; Button shift_button;
Button play_button; Button play_button;
AnalogInput cv1; AnalogInput cv1;