Skip to content

Commit

Permalink
keira: liltracker: improve UI, add page/BPM/length selectors, add no-…
Browse files Browse the repository at this point in the history
…op load/save/reset buttons
  • Loading branch information
and3rson committed Apr 8, 2024
1 parent dfc6f53 commit b72a443
Show file tree
Hide file tree
Showing 9 changed files with 296 additions and 103 deletions.
3 changes: 1 addition & 2 deletions firmware/keira/src/apps/liltracker/config.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#pragma once

#define CHANNEL_COUNT 3
#define BPM 400
#define MS_PER_BEAT 60 * 1000 / BPM
#define DEFAULT_BPM 400
#define CHANNEL_SIZE 32
#define MAX_VOLUME 127
178 changes: 148 additions & 30 deletions firmware/keira/src/apps/liltracker/liltracker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,36 @@
#include "liltracker.h"
#include "note.h"

constexpr int32_t SCORE_ITEM_HEIGHT = 15;
// Layout:
// - Title
// - Controls (3 columns, 2 rows)
// - Score header (3 columns)
// - Score (3 columns, rest of the screen)

// Overall layout
constexpr int32_t ITEM_HEIGHT = 15;

// Score header
constexpr int32_t SCORE_HEADER_TOP = 64;
constexpr int32_t SCORE_TOP = SCORE_HEADER_TOP + SCORE_ITEM_HEIGHT;

// Title
constexpr int32_t TITLE_HEIGHT = 20;

// Controls
constexpr int32_t CONTROL_TOP = TITLE_HEIGHT + (SCORE_HEADER_TOP - TITLE_HEIGHT - ITEM_HEIGHT * 2) / 2;
constexpr int32_t CONTROL_ROWS = 2;
constexpr int32_t CONTROL_COLS = 3;
constexpr int32_t CONTROL_PADDING_LEFT = 32;
const int32_t CONTROL_WIDTH = (lilka::display.width() - CONTROL_PADDING_LEFT) / CONTROL_COLS;

// Score
constexpr int32_t SCORE_TOP = SCORE_HEADER_TOP + ITEM_HEIGHT;
const int32_t SCORE_HEIGHT = lilka::display.height() - SCORE_TOP;
constexpr int32_t SCORE_ITEM_HEIGHT = ITEM_HEIGHT;
const uint8_t* FONT = FONT_8x13_MONO;
const int32_t SCORE_COUNTER_WIDTH = 32;
constexpr int32_t SCORE_COUNTER_WIDTH = 32;
const int32_t SCORE_EVENT_WIDTH = (lilka::display.width() - SCORE_COUNTER_WIDTH) / CHANNEL_COUNT;
const int32_t SCORE_ROW_COUNT = SCORE_HEIGHT / SCORE_ITEM_HEIGHT;
const int32_t SCORE_ROW_COUNT = SCORE_HEIGHT / ITEM_HEIGHT;
const int32_t SCORE_MIDDLE_ROW_INDEX = SCORE_ROW_COUNT / 2;

typedef enum {
Expand Down Expand Up @@ -276,7 +298,7 @@ void LilTrackerApp::run() {
int controlCursorX = 0;
int controlCursorY = 0;

char str[32];
char str[64];

while (1) {
seq_state_t seqState = sequencer.getSeqState();
Expand All @@ -289,23 +311,25 @@ void LilTrackerApp::run() {
page_t* page = track.getPage(pageIndex);

canvas->fillScreen(lilka::colors::Black);
canvas->setFont(FONT);

// // Draw mixer buffer
// // Draw mixed buffer
// int16_t buffer[MIXER_BUFFER_SIZE];
// mixer.readBuffer(buffer);
//
// int16_t prevX, prevY;
// for (int i = 0; i < MIXER_BUFFER_SIZE; i++) {
// int x = i * lilka::display.width() / MIXER_BUFFER_SIZE;
// int y = SCORE_HEADER_TOP / 2 + ((float)buffer[i] / 32768 / mixer.getMasterVolume()) * SCORE_HEADER_TOP / 2;
// float amplitude = static_cast<float>(buffer[i]) / 32768 / mixer.getMasterVolume();
// int y = SCORE_HEADER_TOP / 2 + static_cast<int>(amplitude * SCORE_HEADER_TOP / 2);
// if (i > 0) {
// canvas->drawLine(prevX, prevY, x, y, lilka::colors::White);
// }
// prevX = x;
// prevY = y;
// }

// Draw channel buffers
// Draw per-channel buffers
if (seqState.playing || isPreviewing) {
for (int channelIndex = 0; channelIndex < CHANNEL_COUNT; channelIndex++) {
int16_t buffer[MIXER_BUFFER_SIZE];
Expand All @@ -317,8 +341,8 @@ void LilTrackerApp::run() {
for (int i = 0; i < MIXER_BUFFER_SIZE; i += 4) {
int x = minX + i * width / MIXER_BUFFER_SIZE;
int index = i / 2; // Make samples wider for nicer display
int y = SCORE_HEADER_TOP / 2 +
((float)buffer[index] / 32768 / mixer.getMasterVolume()) * SCORE_HEADER_TOP / 2;
float amplitude = static_cast<float>(buffer[index]) / 32768 / mixer.getMasterVolume();
int y = SCORE_HEADER_TOP / 2 + static_cast<int>(amplitude * SCORE_HEADER_TOP / 2);
if (i > 0) {
canvas->drawLine(prevX, prevY, x, y, lilka::colors::White);
}
Expand All @@ -327,33 +351,93 @@ void LilTrackerApp::run() {
}
}
} else {
// Draw title
canvas->setFont(FONT_10x20);
canvas->setTextColor(lilka::colors::Pink, lilka::colors::Pink);
sprintf(str, "ЛілТрекер by Андерсон");
canvas->drawTextAligned(
str, lilka::display.width() / 2, TITLE_HEIGHT / 2, lilka::ALIGN_CENTER, lilka::ALIGN_CENTER
);

canvas->setFont(FONT);

// Draw current page info and pattern indices
sprintf(str, "Page %02X", pageIndex);
bool isPageSelectorFocused = activeBlock == ACTIVE_BLOCK_CONTROLS && controlCursorY == 0;
sprintf(str, "Page: %02X", pageIndex);
bool isPageSelFocused = activeBlock == ACTIVE_BLOCK_CONTROLS && controlCursorX == 0 && controlCursorY == 0;
printText(
str,
lilka::display.width() / 2,
SCORE_HEADER_TOP / 2,
CONTROL_PADDING_LEFT,
CONTROL_TOP + ITEM_HEIGHT / 2,
lilka::ALIGN_START,
lilka::ALIGN_CENTER,
isPageSelFocused && isEditing,
isPageSelFocused,
false
);

bool isBPMFocused = activeBlock == ACTIVE_BLOCK_CONTROLS && controlCursorX == 1 && controlCursorY == 0;
sprintf(str, "BPM: %3d", track.getBPM());
printText(
str,
CONTROL_PADDING_LEFT + CONTROL_WIDTH,
CONTROL_TOP + ITEM_HEIGHT / 2,
lilka::ALIGN_START,
lilka::ALIGN_CENTER,
isPageSelectorFocused && isEditing,
isPageSelectorFocused,
isBPMFocused && isEditing,
isBPMFocused,
false
);

bool isLengthFocused = activeBlock == ACTIVE_BLOCK_CONTROLS && controlCursorX == 2 && controlCursorY == 0;
sprintf(str, "Len: %3d", track.getPageCount());
printText(
str,
CONTROL_PADDING_LEFT + CONTROL_WIDTH * 2,
CONTROL_TOP + ITEM_HEIGHT / 2,
lilka::ALIGN_START,
lilka::ALIGN_CENTER,
isLengthFocused && isEditing,
isLengthFocused,
false
);

// Draw buttons (load/save/reset)
for (int i = 0; i < 3; i++) {
bool isFocused = activeBlock == ACTIVE_BLOCK_CONTROLS && controlCursorX == i && controlCursorY == 1;
const char* buttonText;
if (i == 0) {
buttonText = "Відкрити";
} else if (i == 1) {
buttonText = "Записати";
} else {
buttonText = "Скинути";
}
canvas->setFont(FONT_8x13);
printText(
buttonText,
CONTROL_PADDING_LEFT + CONTROL_WIDTH * i,
CONTROL_TOP + ITEM_HEIGHT * 3 / 2,
lilka::ALIGN_START,
lilka::ALIGN_CENTER,
isFocused && isEditing,
isFocused,
false
);
}
canvas->setFont(FONT);
}

canvas->setFont(FONT);
// Draw channel waveform names
canvas->setTextBound(0, 0, canvas->width(), canvas->height());
canvas->setTextColor(lilka::colors::White, lilka::colors::White);
canvas->drawTextAligned(
"##", SCORE_COUNTER_WIDTH / 2, SCORE_HEADER_TOP, lilka::ALIGN_CENTER, lilka::ALIGN_START
"##", SCORE_COUNTER_WIDTH / 2, SCORE_HEADER_TOP + ITEM_HEIGHT / 2, lilka::ALIGN_CENTER, lilka::ALIGN_CENTER
);

// Draw channel waveform names
for (int channelIndex = 0; channelIndex < CHANNEL_COUNT; channelIndex++) {
Pattern* pattern = track.getPattern(page->patternIndices[channelIndex]);
bool isChannelWaveformFocused =
activeBlock == ACTIVE_BLOCK_CONTROLS && controlCursorY == 1 && controlCursorX == channelIndex;
activeBlock == ACTIVE_BLOCK_CONTROLS && controlCursorY == 2 && controlCursorX == channelIndex;
sprintf(
str,
"%02X: %-4s",
Expand All @@ -363,9 +447,9 @@ void LilTrackerApp::run() {
printText(
str,
SCORE_COUNTER_WIDTH + channelIndex * SCORE_EVENT_WIDTH,
SCORE_HEADER_TOP,
lilka::ALIGN_START,
SCORE_HEADER_TOP + ITEM_HEIGHT / 2,
lilka::ALIGN_START,
lilka::ALIGN_CENTER,
isChannelWaveformFocused && isEditing,
isChannelWaveformFocused,
false
Expand Down Expand Up @@ -395,7 +479,7 @@ void LilTrackerApp::run() {
canvas->drawRect(0, y, canvas->width(), SCORE_ITEM_HEIGHT, lilka::colors::Blue);
}
canvas->setTextColor(lilka::colors::White, lilka::colors::White);
sprintf(str, "%02X", eventIndex);
sprintf(str, "%2d", eventIndex);
canvas->drawTextAligned(
str, SCORE_COUNTER_WIDTH / 2, y + SCORE_ITEM_HEIGHT / 2, lilka::ALIGN_CENTER, lilka::ALIGN_CENTER
);
Expand Down Expand Up @@ -484,13 +568,45 @@ void LilTrackerApp::run() {
if (state.up.justPressed || state.down.justPressed || state.left.justPressed ||
state.right.justPressed) {
if (controlCursorY == 0) {
// Select page
if (state.up.justPressed || state.left.justPressed) {
pageIndex = (pageIndex - 1 + track.getPageCount()) % track.getPageCount();
} else if (state.down.justPressed || state.right.justPressed) {
pageIndex = (pageIndex + 1) % track.getPageCount();
if (controlCursorX == 0) {
// Select page
if (state.up.justPressed || state.left.justPressed) {
pageIndex = (pageIndex - 1 + track.getPageCount()) % track.getPageCount();
} else if (state.down.justPressed || state.right.justPressed) {
pageIndex = (pageIndex + 1) % track.getPageCount();
}
} else if (controlCursorX == 1) {
// Adjust BPM
if (state.up.justPressed) {
track.setBPM(track.getBPM() + 1);
} else if (state.down.justPressed) {
track.setBPM(track.getBPM() - 1);
} else if (state.left.justPressed) {
track.setBPM(track.getBPM() - 5);
} else if (state.right.justPressed) {
track.setBPM(track.getBPM() + 5);
}
} else if (controlCursorX == 2) {
// Adjust length
if (state.up.justPressed) {
track.setPageCount(track.getPageCount() + 1);
} else if (state.down.justPressed) {
track.setPageCount(track.getPageCount() - 1);
if (pageIndex >= track.getPageCount()) {
// Adjust current page if it's out of bounds
pageIndex = track.getPageCount() - 1;
}
}
}
} else if (controlCursorY == 1) {
if (controlCursorX == 0) {
// Load
} else if (controlCursorX == 1) {
// Save
} else if (controlCursorX == 2) {
// Reset
}
} else if (controlCursorY == 2) {
// Select waveform for pattern's channel
Pattern* pattern = track.getPattern(page->patternIndices[controlCursorX]);
if (state.left.justPressed) {
Expand Down Expand Up @@ -520,10 +636,12 @@ void LilTrackerApp::run() {
isEditing = true;
}
if (state.up.justPressed) {
controlCursorY = (controlCursorY - 1 + 2) % 2;
// We add 1 to CONTROL_ROWS to account for the score header row which is not part of the control, but is managed by the same cursor
controlCursorY = (controlCursorY - 1 + (CONTROL_ROWS + 1)) % (CONTROL_ROWS + 1);
} else if (state.down.justPressed) {
controlCursorY = (controlCursorY + 1) % 2;
controlCursorY = (controlCursorY + 1) % (CONTROL_ROWS + 1);
} else if (state.left.justPressed) {
// TODO: This assumes that control column count is the same as channel count
controlCursorX = (controlCursorX - 1 + CHANNEL_COUNT) % CHANNEL_COUNT;
} else if (state.right.justPressed) {
controlCursorX = (controlCursorX + 1) % CHANNEL_COUNT;
Expand Down
2 changes: 0 additions & 2 deletions firmware/keira/src/apps/liltracker/mixer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,6 @@ void Mixer::mixerTask() {
size_t bytesWritten = 0;
esp_i2s::i2s_write(esp_i2s::I2S_NUM_0, audioBuffer, MIXER_BUFFER_SIZE * 2, &bytesWritten, portMAX_DELAY);
xSemaphoreTake(xMutex, portMAX_DELAY);
int64_t mixCopyStart = micros();
memcpy(audioBufferCopy, audioBuffer, sizeof(int16_t) * MIXER_BUFFER_SIZE);
for (int8_t channelIndex = 0; channelIndex < CHANNEL_COUNT; channelIndex++) {
memcpy(
Expand All @@ -187,7 +186,6 @@ void Mixer::mixerTask() {
sizeof(int16_t) * MIXER_BUFFER_SIZE
);
}
int64_t mixCopyEnd = micros();
xSemaphoreGive(xMutex);
time += bytesWritten / 2;
// taskYIELD();
Expand Down
Loading

0 comments on commit b72a443

Please sign in to comment.