feat: Introduce global menu for clock and CV destination parameters, integrate CV modulation, and refactor display rendering.
This commit is contained in:
@ -23,6 +23,37 @@ const unsigned long SAVE_DELAY_MS = 5000;
|
|||||||
bool eeprom_needs_save = false;
|
bool eeprom_needs_save = false;
|
||||||
unsigned long last_param_change = 0;
|
unsigned long last_param_change = 0;
|
||||||
|
|
||||||
|
// Menus and State
|
||||||
|
enum MenuPage {
|
||||||
|
PAGE_PATTERN = 0,
|
||||||
|
PAGE_GLOBAL = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum GlobalParam {
|
||||||
|
PARAM_GLOBAL_CLK_SRC = 0,
|
||||||
|
PARAM_GLOBAL_BPM = 1,
|
||||||
|
PARAM_GLOBAL_CV1_DEST = 2,
|
||||||
|
PARAM_GLOBAL_CV2_DEST = 3,
|
||||||
|
PARAM_GLOBAL_LAST = 4
|
||||||
|
};
|
||||||
|
|
||||||
|
enum CvDest {
|
||||||
|
CV_DEST_NONE = 0,
|
||||||
|
CV_DEST_KICK_DENS = 1,
|
||||||
|
CV_DEST_SNARE_DENS = 2,
|
||||||
|
CV_DEST_HHAT_DENS = 3,
|
||||||
|
CV_DEST_CHAOS = 4,
|
||||||
|
CV_DEST_MAP_X = 5,
|
||||||
|
CV_DEST_MAP_Y = 6,
|
||||||
|
CV_DEST_LAST = 7
|
||||||
|
};
|
||||||
|
|
||||||
|
MenuPage current_page = PAGE_PATTERN;
|
||||||
|
GlobalParam current_global_param = PARAM_GLOBAL_CLK_SRC;
|
||||||
|
CvDest cv1_dest = CV_DEST_MAP_X;
|
||||||
|
CvDest cv2_dest = CV_DEST_CHAOS;
|
||||||
|
Clock::Source selected_source = Clock::SOURCE_INTERNAL;
|
||||||
|
|
||||||
// UI & Navigation
|
// UI & Navigation
|
||||||
enum SelectedParam {
|
enum SelectedParam {
|
||||||
PARAM_KICK_DENS = 0,
|
PARAM_KICK_DENS = 0,
|
||||||
@ -42,7 +73,6 @@ const unsigned long REDRAW_DELAY_MS = 30; // ~33fps limit
|
|||||||
|
|
||||||
// Sequencer State
|
// Sequencer State
|
||||||
int current_step = 0;
|
int current_step = 0;
|
||||||
bool is_playing = false;
|
|
||||||
|
|
||||||
// Engine Parameters (0-255)
|
// Engine Parameters (0-255)
|
||||||
int inst_density[3] = {128, 128,
|
int inst_density[3] = {128, 128,
|
||||||
@ -104,6 +134,12 @@ void LoadState() {
|
|||||||
EEPROM.get(EEPROM_MAP_X, map_x);
|
EEPROM.get(EEPROM_MAP_X, map_x);
|
||||||
EEPROM.get(EEPROM_MAP_Y, map_y);
|
EEPROM.get(EEPROM_MAP_Y, map_y);
|
||||||
EEPROM.get(EEPROM_CHAOS, chaos_amount);
|
EEPROM.get(EEPROM_CHAOS, chaos_amount);
|
||||||
|
|
||||||
|
// We can just add read for newly added properties.
|
||||||
|
// Wait until later to strictly map out addresses manually
|
||||||
|
// or just let EEPROM.get/put figure it out relative to addresses.
|
||||||
|
// For simplicity, let's keep it safe. We will increment the EEPROM_INIT_FLAG
|
||||||
|
// to 0xAD and append the new values after EEPROM_CHAOS.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,13 +184,37 @@ void ProcessSequencerTick(uint32_t tick) {
|
|||||||
// Handle new 16th note step
|
// Handle new 16th note step
|
||||||
if (tick % PULSES_PER_16TH == 0) {
|
if (tick % PULSES_PER_16TH == 0) {
|
||||||
|
|
||||||
int mod_map_x = constrain(map_x + (cv1_val / 2), 0, 255);
|
int active_map_x = map_x;
|
||||||
int active_chaos = constrain(chaos_amount + (cv2_val / 2), 0, 255);
|
int active_map_y = map_y;
|
||||||
|
int active_chaos = chaos_amount;
|
||||||
|
int active_dens[3] = { inst_density[0], inst_density[1], inst_density[2] };
|
||||||
|
|
||||||
|
// Apply CV1
|
||||||
|
switch (cv1_dest) {
|
||||||
|
case CV_DEST_KICK_DENS: active_dens[0] = constrain(active_dens[0] + (cv1_val / 4), 0, 255); break;
|
||||||
|
case CV_DEST_SNARE_DENS: active_dens[1] = constrain(active_dens[1] + (cv1_val / 4), 0, 255); break;
|
||||||
|
case CV_DEST_HHAT_DENS: active_dens[2] = constrain(active_dens[2] + (cv1_val / 4), 0, 255); break;
|
||||||
|
case CV_DEST_CHAOS: active_chaos = constrain(active_chaos + (cv1_val / 4), 0, 255); break;
|
||||||
|
case CV_DEST_MAP_X: active_map_x = constrain(active_map_x + (cv1_val / 4), 0, 255); break;
|
||||||
|
case CV_DEST_MAP_Y: active_map_y = constrain(active_map_y + (cv1_val / 4), 0, 255); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply CV2
|
||||||
|
switch (cv2_dest) {
|
||||||
|
case CV_DEST_KICK_DENS: active_dens[0] = constrain(active_dens[0] + (cv2_val / 4), 0, 255); break;
|
||||||
|
case CV_DEST_SNARE_DENS: active_dens[1] = constrain(active_dens[1] + (cv2_val / 4), 0, 255); break;
|
||||||
|
case CV_DEST_HHAT_DENS: active_dens[2] = constrain(active_dens[2] + (cv2_val / 4), 0, 255); break;
|
||||||
|
case CV_DEST_CHAOS: active_chaos = constrain(active_chaos + (cv2_val / 4), 0, 255); break;
|
||||||
|
case CV_DEST_MAP_X: active_map_x = constrain(active_map_x + (cv2_val / 4), 0, 255); break;
|
||||||
|
case CV_DEST_MAP_Y: active_map_y = constrain(active_map_y + (cv2_val / 4), 0, 255); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
// Evaluate hits for Kick, Snare, HiHats
|
// Evaluate hits for Kick, Snare, HiHats
|
||||||
for (int inst = 0; inst < 3; inst++) {
|
for (int inst = 0; inst < 3; inst++) {
|
||||||
uint8_t threshold = GetThreshold(inst, current_step, mod_map_x, map_y);
|
uint8_t threshold = GetThreshold(inst, current_step, active_map_x, active_map_y);
|
||||||
int active_density = inst_density[inst];
|
int active_density = active_dens[inst];
|
||||||
|
|
||||||
// Inject chaos
|
// Inject chaos
|
||||||
if (active_chaos > 0) {
|
if (active_chaos > 0) {
|
||||||
@ -187,14 +247,13 @@ void ProcessSequencerTick(uint32_t tick) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void OnPlayPress() {
|
void OnPlayPress() {
|
||||||
if (is_playing) {
|
if (!gravity.clock.IsPaused()) {
|
||||||
gravity.clock.Stop();
|
gravity.clock.Stop();
|
||||||
for (int i = 0; i < 6; i++)
|
for (int i = 0; i < 6; i++)
|
||||||
gravity.outputs[i].Low();
|
gravity.outputs[i].Low();
|
||||||
} else {
|
} else {
|
||||||
gravity.clock.Start();
|
gravity.clock.Start();
|
||||||
}
|
}
|
||||||
is_playing = !is_playing;
|
|
||||||
needs_redraw = true;
|
needs_redraw = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,37 +262,103 @@ void OnEncoderPress() {
|
|||||||
needs_redraw = true;
|
needs_redraw = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OnEncoderPressRotate(int val) {
|
||||||
|
// Toggle between Pattern and Global pages
|
||||||
|
if (val > 0) {
|
||||||
|
current_page = PAGE_GLOBAL;
|
||||||
|
} else {
|
||||||
|
current_page = PAGE_PATTERN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset editing state when swapping pages
|
||||||
|
editing_param = false;
|
||||||
|
needs_redraw = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map CV Destination enum string labels
|
||||||
|
const char* GetCvDestName(CvDest dest) {
|
||||||
|
switch(dest) {
|
||||||
|
case CV_DEST_NONE: return "NONE";
|
||||||
|
case CV_DEST_KICK_DENS: return "KICK DENS";
|
||||||
|
case CV_DEST_SNARE_DENS: return "SNAR DENS";
|
||||||
|
case CV_DEST_HHAT_DENS: return "HHAT DENS";
|
||||||
|
case CV_DEST_CHAOS: return "CHAOS";
|
||||||
|
case CV_DEST_MAP_X: return "MAP X";
|
||||||
|
case CV_DEST_MAP_Y: return "MAP Y";
|
||||||
|
default: return "UNKNOWN";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void OnEncoderRotate(int val) {
|
void OnEncoderRotate(int val) {
|
||||||
if (!editing_param) {
|
if (!editing_param) {
|
||||||
// Navigate menu (clamp to edges, do not wrap)
|
// Navigate menu (clamp to edges, do not wrap)
|
||||||
int next_param = (int)current_param + val;
|
if (current_page == PAGE_PATTERN) {
|
||||||
next_param = constrain(next_param, 0, PARAM_LAST - 1);
|
int next_param = (int)current_param + val;
|
||||||
current_param = (SelectedParam)next_param;
|
next_param = constrain(next_param, 0, PARAM_LAST - 1);
|
||||||
|
current_param = (SelectedParam)next_param;
|
||||||
|
} else {
|
||||||
|
int next_param = (int)current_global_param + val;
|
||||||
|
next_param = constrain(next_param, 0, PARAM_GLOBAL_LAST - 1);
|
||||||
|
current_global_param = (GlobalParam)next_param;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Edit parameter
|
// Edit parameter
|
||||||
int amt = val * 8; // Adjust by 8 values at a time for speed mapping
|
if (current_page == PAGE_PATTERN) {
|
||||||
|
int amt = val * 8; // Adjust by 8 values at a time for speed mapping
|
||||||
|
|
||||||
switch (current_param) {
|
switch (current_param) {
|
||||||
case PARAM_KICK_DENS:
|
case PARAM_KICK_DENS:
|
||||||
inst_density[0] = constrain(inst_density[0] + amt, 0, 255);
|
inst_density[0] = constrain(inst_density[0] + amt, 0, 255);
|
||||||
break;
|
break;
|
||||||
case PARAM_SNARE_DENS:
|
case PARAM_SNARE_DENS:
|
||||||
inst_density[1] = constrain(inst_density[1] + amt, 0, 255);
|
inst_density[1] = constrain(inst_density[1] + amt, 0, 255);
|
||||||
break;
|
break;
|
||||||
case PARAM_HIHAT_DENS:
|
case PARAM_HIHAT_DENS:
|
||||||
inst_density[2] = constrain(inst_density[2] + amt, 0, 255);
|
inst_density[2] = constrain(inst_density[2] + amt, 0, 255);
|
||||||
break;
|
break;
|
||||||
case PARAM_CHAOS:
|
case PARAM_CHAOS:
|
||||||
chaos_amount = constrain(chaos_amount + amt, 0, 255);
|
chaos_amount = constrain(chaos_amount + amt, 0, 255);
|
||||||
break;
|
break;
|
||||||
case PARAM_MAP_X:
|
case PARAM_MAP_X:
|
||||||
map_x = constrain(map_x + amt, 0, 255);
|
map_x = constrain(map_x + amt, 0, 255);
|
||||||
break;
|
break;
|
||||||
case PARAM_MAP_Y:
|
case PARAM_MAP_Y:
|
||||||
map_y = constrain(map_y + amt, 0, 255);
|
map_y = constrain(map_y + amt, 0, 255);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Edit Global Parameter
|
||||||
|
switch (current_global_param) {
|
||||||
|
case PARAM_GLOBAL_CLK_SRC: {
|
||||||
|
int src = (int)selected_source + val;
|
||||||
|
src = constrain(src, 0, Clock::SOURCE_LAST - 1);
|
||||||
|
selected_source = (Clock::Source)src;
|
||||||
|
gravity.clock.SetSource(selected_source);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PARAM_GLOBAL_BPM: {
|
||||||
|
if (!gravity.clock.ExternalSource()) {
|
||||||
|
gravity.clock.SetTempo(gravity.clock.Tempo() + val);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PARAM_GLOBAL_CV1_DEST: {
|
||||||
|
int dest = (int)cv1_dest + val;
|
||||||
|
dest = constrain(dest, 0, CV_DEST_LAST - 1);
|
||||||
|
cv1_dest = (CvDest)dest;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PARAM_GLOBAL_CV2_DEST: {
|
||||||
|
int dest = (int)cv2_dest + val;
|
||||||
|
dest = constrain(dest, 0, CV_DEST_LAST - 1);
|
||||||
|
cv2_dest = (CvDest)dest;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
eeprom_needs_save = true;
|
eeprom_needs_save = true;
|
||||||
@ -242,92 +367,225 @@ void OnEncoderRotate(int val) {
|
|||||||
needs_redraw = true;
|
needs_redraw = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawBarGraph(int y, const char *label, int value, bool is_selected) {
|
#include "display_assets.h"
|
||||||
// Reset draw color to default foreground
|
|
||||||
gravity.display.setDrawColor(1);
|
|
||||||
gravity.display.setCursor(0, y);
|
|
||||||
|
|
||||||
if (is_selected) {
|
constexpr uint8_t SCREEN_CENTER_X = 32;
|
||||||
gravity.display.print(">");
|
constexpr uint8_t MAIN_TEXT_Y = 26;
|
||||||
if (editing_param) {
|
constexpr uint8_t SUB_TEXT_Y = 40;
|
||||||
// Draw solid white box behind the label
|
constexpr uint8_t VISIBLE_MENU_ITEMS = 3;
|
||||||
gravity.display.drawBox(6, y - 8, 26, 10);
|
constexpr uint8_t MENU_ITEM_HEIGHT = 14;
|
||||||
// Switch to black text to 'cut out' the label from the box
|
constexpr uint8_t MENU_BOX_PADDING = 4;
|
||||||
gravity.display.setDrawColor(0);
|
constexpr uint8_t MENU_BOX_WIDTH = 64;
|
||||||
}
|
|
||||||
} else {
|
void drawCenteredText(const char *text, int y, const uint8_t *font) {
|
||||||
gravity.display.print(" ");
|
gravity.display.setFont(font);
|
||||||
|
int textWidth = gravity.display.getStrWidth(text);
|
||||||
|
gravity.display.drawStr(SCREEN_CENTER_X - (textWidth / 2), y, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawRightAlignedText(const char *text, int y) {
|
||||||
|
int textWidth = gravity.display.getStrWidth(text);
|
||||||
|
int drawX = (SCREEN_WIDTH - textWidth) - MENU_BOX_PADDING;
|
||||||
|
gravity.display.drawStr(drawX, y, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawMainSelection() {
|
||||||
|
gravity.display.setDrawColor(1);
|
||||||
|
const int tickSize = 3;
|
||||||
|
const int mainWidth = SCREEN_WIDTH / 2;
|
||||||
|
const int mainHeight = 49;
|
||||||
|
gravity.display.drawLine(0, 0, tickSize, 0);
|
||||||
|
gravity.display.drawLine(0, 0, 0, tickSize);
|
||||||
|
gravity.display.drawLine(mainWidth, 0, mainWidth - tickSize, 0);
|
||||||
|
gravity.display.drawLine(mainWidth, 0, mainWidth, tickSize);
|
||||||
|
gravity.display.drawLine(mainWidth, mainHeight, mainWidth,
|
||||||
|
mainHeight - tickSize);
|
||||||
|
gravity.display.drawLine(mainWidth, mainHeight, mainWidth - tickSize,
|
||||||
|
mainHeight);
|
||||||
|
gravity.display.drawLine(0, mainHeight, tickSize, mainHeight);
|
||||||
|
gravity.display.drawLine(0, mainHeight, 0, mainHeight - tickSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawMenuItems(String menu_items[], int menu_size, int current_item) {
|
||||||
|
gravity.display.setFont(TEXT_FONT);
|
||||||
|
|
||||||
|
int selectedBoxY = 0;
|
||||||
|
if (menu_size >= VISIBLE_MENU_ITEMS && current_item == menu_size - 1) {
|
||||||
|
selectedBoxY = MENU_ITEM_HEIGHT * min(2, current_item);
|
||||||
|
} else if (current_item > 0) {
|
||||||
|
selectedBoxY = MENU_ITEM_HEIGHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
gravity.display.setCursor(6, y);
|
int boxX = MENU_BOX_WIDTH + 1;
|
||||||
gravity.display.print(label);
|
int boxY = selectedBoxY + 2;
|
||||||
|
int boxWidth = MENU_BOX_WIDTH - 1;
|
||||||
|
int boxHeight = MENU_ITEM_HEIGHT + 1;
|
||||||
|
|
||||||
// Restore draw color to white for the bar and text
|
|
||||||
gravity.display.setDrawColor(1);
|
gravity.display.setDrawColor(1);
|
||||||
|
if (editing_param) {
|
||||||
|
gravity.display.drawBox(boxX, boxY, boxWidth, boxHeight);
|
||||||
|
drawMainSelection();
|
||||||
|
} else {
|
||||||
|
gravity.display.drawFrame(boxX, boxY, boxWidth, boxHeight);
|
||||||
|
}
|
||||||
|
|
||||||
// Draw Bar
|
gravity.display.setDrawColor(2);
|
||||||
int barLen = map(value, 0, 255, 0, 60);
|
int start_index = 0;
|
||||||
gravity.display.drawFrame(34, y - 8, 60, 8);
|
if (menu_size >= VISIBLE_MENU_ITEMS && current_item == menu_size - 1) {
|
||||||
gravity.display.drawBox(34, y - 8, barLen, 8);
|
start_index = menu_size - VISIBLE_MENU_ITEMS;
|
||||||
|
} else if (current_item > 0) {
|
||||||
|
start_index = current_item - 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Draw value percentage
|
for (int i = 0; i < min(menu_size, (int)VISIBLE_MENU_ITEMS); ++i) {
|
||||||
gravity.display.setCursor(98, y);
|
int idx = start_index + i;
|
||||||
int pct = map(value, 0, 255, 0, 100);
|
drawRightAlignedText(menu_items[idx].c_str(),
|
||||||
gravity.display.print(pct);
|
MENU_ITEM_HEIGHT * (i + 1) - 1);
|
||||||
gravity.display.print("%");
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayMainArea() {
|
||||||
|
gravity.display.setFontMode(1);
|
||||||
|
gravity.display.setDrawColor(2);
|
||||||
|
|
||||||
|
String mainText;
|
||||||
|
String subText;
|
||||||
|
|
||||||
|
if (current_page == PAGE_PATTERN) {
|
||||||
|
switch (current_param) {
|
||||||
|
case PARAM_KICK_DENS:
|
||||||
|
mainText = String(map(inst_density[0], 0, 255, 0, 100)) + "%";
|
||||||
|
subText = "KICK DENS";
|
||||||
|
break;
|
||||||
|
case PARAM_SNARE_DENS:
|
||||||
|
mainText = String(map(inst_density[1], 0, 255, 0, 100)) + "%";
|
||||||
|
subText = "SNAR DENS";
|
||||||
|
break;
|
||||||
|
case PARAM_HIHAT_DENS:
|
||||||
|
mainText = String(map(inst_density[2], 0, 255, 0, 100)) + "%";
|
||||||
|
subText = "HHAT DENS";
|
||||||
|
break;
|
||||||
|
case PARAM_CHAOS:
|
||||||
|
mainText = String(map(chaos_amount, 0, 255, 0, 100)) + "%";
|
||||||
|
subText = "CHAOS";
|
||||||
|
break;
|
||||||
|
case PARAM_MAP_X:
|
||||||
|
mainText = String(map_x);
|
||||||
|
subText = "MAP X";
|
||||||
|
break;
|
||||||
|
case PARAM_MAP_Y:
|
||||||
|
mainText = String(map_y);
|
||||||
|
subText = "MAP Y";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawCenteredText(mainText.c_str(), MAIN_TEXT_Y, LARGE_FONT);
|
||||||
|
drawCenteredText(subText.c_str(), SUB_TEXT_Y, TEXT_FONT);
|
||||||
|
|
||||||
|
String menu_items[PARAM_LAST] = {
|
||||||
|
"KICK", "SNARE", "HHAT", "CHAOS", "MAP X", "MAP Y"};
|
||||||
|
drawMenuItems(menu_items, PARAM_LAST, (int)current_param);
|
||||||
|
|
||||||
|
} else if (current_page == PAGE_GLOBAL) {
|
||||||
|
switch (current_global_param) {
|
||||||
|
case PARAM_GLOBAL_CLK_SRC:
|
||||||
|
switch (selected_source) {
|
||||||
|
case Clock::SOURCE_INTERNAL:
|
||||||
|
mainText = F("INT");
|
||||||
|
subText = F("CLOCK");
|
||||||
|
break;
|
||||||
|
case Clock::SOURCE_EXTERNAL_PPQN_24:
|
||||||
|
mainText = F("EXT");
|
||||||
|
subText = F("24 PPQN");
|
||||||
|
break;
|
||||||
|
case Clock::SOURCE_EXTERNAL_PPQN_4:
|
||||||
|
mainText = F("EXT");
|
||||||
|
subText = F("4 PPQN");
|
||||||
|
break;
|
||||||
|
case Clock::SOURCE_EXTERNAL_PPQN_2:
|
||||||
|
mainText = F("EXT");
|
||||||
|
subText = F("2 PPQN");
|
||||||
|
break;
|
||||||
|
case Clock::SOURCE_EXTERNAL_PPQN_1:
|
||||||
|
mainText = F("EXT");
|
||||||
|
subText = F("1 PPQN");
|
||||||
|
break;
|
||||||
|
case Clock::SOURCE_EXTERNAL_MIDI:
|
||||||
|
mainText = F("EXT");
|
||||||
|
subText = F("MIDI");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PARAM_GLOBAL_BPM:
|
||||||
|
if (gravity.clock.ExternalSource()) {
|
||||||
|
mainText = F("EXT");
|
||||||
|
} else {
|
||||||
|
mainText = String(gravity.clock.Tempo());
|
||||||
|
}
|
||||||
|
subText = F("BPM");
|
||||||
|
break;
|
||||||
|
case PARAM_GLOBAL_CV1_DEST:
|
||||||
|
mainText = F("CV1");
|
||||||
|
subText = GetCvDestName(cv1_dest);
|
||||||
|
break;
|
||||||
|
case PARAM_GLOBAL_CV2_DEST:
|
||||||
|
mainText = F("CV2");
|
||||||
|
subText = GetCvDestName(cv2_dest);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawCenteredText(mainText.c_str(), MAIN_TEXT_Y, LARGE_FONT);
|
||||||
|
drawCenteredText(subText.c_str(), SUB_TEXT_Y, TEXT_FONT);
|
||||||
|
|
||||||
|
String menu_items[PARAM_GLOBAL_LAST] = {
|
||||||
|
"SOURCE", "TEMPO", "CV1 DEST", "CV2 DEST"};
|
||||||
|
drawMenuItems(menu_items, PARAM_GLOBAL_LAST, (int)current_global_param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisplayBottomBar() {
|
||||||
|
int boxY = 50;
|
||||||
|
int boxHeight = 14;
|
||||||
|
int boxX_Pattern = 18;
|
||||||
|
int boxWidth = 55;
|
||||||
|
int boxX_Global = boxX_Pattern + boxWidth;
|
||||||
|
|
||||||
|
gravity.display.setDrawColor(1);
|
||||||
|
gravity.display.drawHLine(1, boxY, SCREEN_WIDTH - 2);
|
||||||
|
gravity.display.drawVLine(SCREEN_WIDTH - 1, boxY, boxHeight);
|
||||||
|
|
||||||
|
if (current_page == PAGE_PATTERN) {
|
||||||
|
gravity.display.drawBox(boxX_Pattern, boxY, boxWidth, boxHeight);
|
||||||
|
gravity.display.drawVLine(boxX_Global, boxY, boxHeight);
|
||||||
|
} else {
|
||||||
|
gravity.display.drawVLine(boxX_Pattern, boxY, boxHeight);
|
||||||
|
gravity.display.drawBox(boxX_Global, boxY, boxWidth, boxHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
gravity.display.setDrawColor(2);
|
||||||
|
gravity.display.setBitmapMode(1);
|
||||||
|
auto icon = gravity.clock.IsPaused() ? pause_icon : play_icon;
|
||||||
|
gravity.display.drawXBMP(2, boxY, play_icon_width, play_icon_height, icon);
|
||||||
|
|
||||||
|
gravity.display.setFont(TEXT_FONT);
|
||||||
|
|
||||||
|
int patW = gravity.display.getStrWidth("PATTERN");
|
||||||
|
int patX = boxX_Pattern + (boxWidth / 2) - (patW / 2);
|
||||||
|
gravity.display.drawStr(patX, 64 - 3, "PATTERN");
|
||||||
|
|
||||||
|
int gloW = gravity.display.getStrWidth("GLOBAL");
|
||||||
|
int gloX = boxX_Global + (boxWidth / 2) - (gloW / 2);
|
||||||
|
gravity.display.drawStr(gloX, 64 - 3, "GLOBAL");
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateDisplay() {
|
void UpdateDisplay() {
|
||||||
gravity.display.setFontMode(1);
|
gravity.display.setFontMode(1);
|
||||||
gravity.display.setDrawColor(1);
|
DisplayMainArea();
|
||||||
gravity.display.setFont(u8g2_font_5x7_tf);
|
DisplayBottomBar();
|
||||||
|
|
||||||
// Header
|
|
||||||
gravity.display.setCursor(0, 7);
|
|
||||||
if (is_playing)
|
|
||||||
gravity.display.print("[>] PLAY");
|
|
||||||
else
|
|
||||||
gravity.display.print("[||] PAUS");
|
|
||||||
|
|
||||||
gravity.display.setCursor(55, 7);
|
|
||||||
gravity.display.print("BPM:");
|
|
||||||
gravity.display.print(gravity.clock.Tempo());
|
|
||||||
|
|
||||||
gravity.display.drawHLine(0, 10, 128);
|
|
||||||
|
|
||||||
// Parameters List (Scrollable window of 5 items)
|
|
||||||
int y_start = 20;
|
|
||||||
int y_spacing = 9;
|
|
||||||
|
|
||||||
// Calculate window start index
|
|
||||||
int window_start = max(0, min((int)current_param - 2, PARAM_LAST - 5));
|
|
||||||
|
|
||||||
for (int i = 0; i < 5; i++) {
|
|
||||||
int param_idx = window_start + i;
|
|
||||||
int y_pos = y_start + (y_spacing * i);
|
|
||||||
bool is_sel = (current_param == param_idx);
|
|
||||||
|
|
||||||
switch (param_idx) {
|
|
||||||
case PARAM_KICK_DENS:
|
|
||||||
DrawBarGraph(y_pos, "KICK", inst_density[0], is_sel);
|
|
||||||
break;
|
|
||||||
case PARAM_SNARE_DENS:
|
|
||||||
DrawBarGraph(y_pos, "SNAR", inst_density[1], is_sel);
|
|
||||||
break;
|
|
||||||
case PARAM_HIHAT_DENS:
|
|
||||||
DrawBarGraph(y_pos, "HHAT", inst_density[2], is_sel);
|
|
||||||
break;
|
|
||||||
case PARAM_CHAOS:
|
|
||||||
DrawBarGraph(y_pos, "CHAO", chaos_amount, is_sel);
|
|
||||||
break;
|
|
||||||
case PARAM_MAP_X:
|
|
||||||
DrawBarGraph(y_pos, "MAPX", map_x, is_sel);
|
|
||||||
break;
|
|
||||||
case PARAM_MAP_Y:
|
|
||||||
DrawBarGraph(y_pos, "MAPY", map_y, is_sel);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
@ -337,6 +595,7 @@ void setup() {
|
|||||||
gravity.play_button.AttachPressHandler(OnPlayPress);
|
gravity.play_button.AttachPressHandler(OnPlayPress);
|
||||||
gravity.encoder.AttachPressHandler(OnEncoderPress);
|
gravity.encoder.AttachPressHandler(OnEncoderPress);
|
||||||
gravity.encoder.AttachRotateHandler(OnEncoderRotate);
|
gravity.encoder.AttachRotateHandler(OnEncoderRotate);
|
||||||
|
gravity.encoder.AttachPressRotateHandler(OnEncoderPressRotate);
|
||||||
|
|
||||||
gravity.clock.AttachIntHandler(ProcessSequencerTick);
|
gravity.clock.AttachIntHandler(ProcessSequencerTick);
|
||||||
// Default to 120 BPM internal
|
// Default to 120 BPM internal
|
||||||
|
|||||||
90
firmware/Rhythm/display_assets.h
Normal file
90
firmware/Rhythm/display_assets.h
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* Font: velvetscreen.bdf 9pt
|
||||||
|
* https://stncrn.github.io/u8g2-unifont-helper/
|
||||||
|
* "%/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
*/
|
||||||
|
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"
|
||||||
|
"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"
|
||||||
|
"\66J*"
|
||||||
|
"\217\0\63\11\254l\66J\32\215\4\64\10\254l\242\34\272\0\65\11\254l\206\336h"
|
||||||
|
"$\0\66"
|
||||||
|
"\11\254\354T^\61)\0\67\10\254lF\216u\4\70\11\254\354TL*&"
|
||||||
|
"\5\71\11\254\354TL;"
|
||||||
|
")\0:\6\231UR\0A\10\254\354T\34S\6B\11\254lV\34)\216\4C\11\254\354T\324\61"
|
||||||
|
")\0D\10\254lV\64G\2E\10\254l\206\36z\4F\10\254l\206^\71\3G\11\254\354TN"
|
||||||
|
"\63)"
|
||||||
|
"\0H\10\254l\242\34S\6I\6\251T\206\0J\10\254\354k\231\24\0K\11\254l\242J\62"
|
||||||
|
"\225\1L\7\254lr{\4M\11\255t\362ZI\353\0N\11\255t\362TI\356\0O\10\254\354T"
|
||||||
|
"\64\223\2P\11\254lV\34)"
|
||||||
|
"g\0Q\10\254\354T\264b\12R\10\254lV\34\251\31S\11\254\354"
|
||||||
|
"FF\32\215\4T\7\253dVl\1U\10\254l\242\63)\0V\11\255t\262Ne\312\21W\12\255"
|
||||||
|
"t\262J*\251.\0X\11\254l\242L*\312\0Y\12\255tr\252\63\312(\2Z\7\253df*"
|
||||||
|
"\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";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Font: STK-L.bdf 36pt
|
||||||
|
* https://stncrn.github.io/u8g2-unifont-helper/
|
||||||
|
* "%/0123456789ABCDEFILNORSTUVXx"
|
||||||
|
*/
|
||||||
|
const uint8_t LARGE_FONT[766] U8G2_FONT_SECTION("stk-l") =
|
||||||
|
"\35\0\4\4\4\5\3\1\6\20\30\0\0\27\0\0\0\1\77\0\0\2\341%'\17;\226\261\245FL"
|
||||||
|
"\64B\214\30\22\223\220)"
|
||||||
|
"Bj\10Q\232\214\42R\206\310\210\21d\304\30\32a\254\304\270!\0/\14"
|
||||||
|
"\272\272\275\311H\321g\343\306\1\60\37|\373\35CJT\20:"
|
||||||
|
"fW\207\320\210\60\42\304\204\30D\247"
|
||||||
|
"\214\331\354\20\11%"
|
||||||
|
"\212\314\0\61\24z\275\245a\244\12\231\71\63b\214\220q\363\377(E\6\62\33|"
|
||||||
|
"\373\35ShT\20:fl\344\14\211\231\301\306T\71\202#g\371\340\201\1\63\34|"
|
||||||
|
"\373\35ShT"
|
||||||
|
"\20:fl\344@r\264\263\222\344,\215\35\42\241\6\225\31\0\64 "
|
||||||
|
"|\373-!\203\206\214!\62\204"
|
||||||
|
"\314\220A#\10\215\30\65b\324\210Q\306\354\354\1\213\225\363\1\65\32|"
|
||||||
|
"\373\15\25[\214\234/\10)"
|
||||||
|
"Y\61j\350\310Y\32;DB\15*\63\0\66\33}\33\236SiV\14;gt^\230Y\302\202\324"
|
||||||
|
"\71\273;EbM\252\63\0\67\23|\373\205\25\17R\316\207\344\350p\312\201#"
|
||||||
|
"\347\35\0\70 |\373"
|
||||||
|
"\35ShT\20:f\331!\22D\310 "
|
||||||
|
":\205\206\10\11B\307\354\354\20\11\65\250\314\0\71\32|\373"
|
||||||
|
"\35ShT\20:fg\207H,Q\223r\276\30DB\15*\63\0A\26}\33\246r\247\322P\62"
|
||||||
|
"j\310\250\21\343\354\335\203\357\354w\3B$}"
|
||||||
|
"\33\206Dj\226\214\42\61l\304\260\21\303F\14\33\61"
|
||||||
|
"\212\304\222MF\221\30v\316\236=\10\301b\11\0C\27}"
|
||||||
|
"\33\236Si\226\20Bft\376O\211\215"
|
||||||
|
" Db\215\42$\0D\33}\33\206Dj\226\214\32\62l\304\260\21\343\354\177vl\304("
|
||||||
|
"\22K\324"
|
||||||
|
"$\2E\22|\373\205\17R\316KD\30\215\234_>x`\0F\20|"
|
||||||
|
"\373\205\17R\316\227i\262\31"
|
||||||
|
"\71\377\22\0I\7s\333\204\77HL\15{\333\205\201\363\377\77|\360`\0N$}"
|
||||||
|
"\33\6\201\346\314"
|
||||||
|
"\35;\206\12U\242D&\306\230\30cd\210\221!fF\230\31a(+\314\256\63\67\0O\26}"
|
||||||
|
"\33"
|
||||||
|
"\236Si\226\214\32\61\316\376\277\33\61j\310\232Tg\0R\61\216;\6Ek\230\14#"
|
||||||
|
"\61n\304\270"
|
||||||
|
"\21\343F\214\33\61n\304\60\22\243\210\60Q\224j\310\260\61\243\306\20\232"
|
||||||
|
"\325\230QD\206\221\30\67b"
|
||||||
|
"\334\301\1S\42\216;\236c\211\226\220\42\61n\304\270\21c\307R\232,["
|
||||||
|
"\262\203\307\216\65h\16\25"
|
||||||
|
"\21&\253\320\0T\15}\33\206\17R\15\235\377\377\25\0U\21|"
|
||||||
|
"\373\205a\366\377\237\215\30\64D\15"
|
||||||
|
"*\63\0V\26\177\371\205\221\366\377\313\21\343\206\220\42C\25\11r'"
|
||||||
|
"\313\16\3X)~;\206\201\6"
|
||||||
|
"\217\221\30\66\204\20\31\42\244\206\14Cg\320$Q\222\6\315!"
|
||||||
|
"\33\62\212\10\31BD\206\215 v\320"
|
||||||
|
"\302\1x\24\312\272\205A\206\216\220@c\212\224\31$"
|
||||||
|
"S\14\262h\0\0\0\0\4\377\377\0";
|
||||||
|
|
||||||
|
#define play_icon_width 14
|
||||||
|
#define play_icon_height 14
|
||||||
|
static const unsigned char play_icon[28] PROGMEM = {
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x3C, 0x00, 0x7C, 0x00, 0xFC, 0x00,
|
||||||
|
0xFC, 0x03, 0xFC, 0x0F, 0xFC, 0x0F, 0xFC, 0x03, 0xFC, 0x00,
|
||||||
|
0x7C, 0x00, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||||
|
static const unsigned char pause_icon[28] PROGMEM = {
|
||||||
|
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, 0x00, 0x00};
|
||||||
Reference in New Issue
Block a user