diff --git a/src/deluge/gui/ui/keyboard/keyboard_screen.cpp b/src/deluge/gui/ui/keyboard/keyboard_screen.cpp
index ae59d9b515..31dac01810 100644
--- a/src/deluge/gui/ui/keyboard/keyboard_screen.cpp
+++ b/src/deluge/gui/ui/keyboard/keyboard_screen.cpp
@@ -24,6 +24,7 @@
#include "gui/views/arranger_view.h"
#include "gui/views/automation_view.h"
#include "gui/views/instrument_clip_view.h"
+#include "gui/views/navigation_view.h"
#include "gui/views/session_view.h"
#include "gui/views/view.h"
#include "hid/buttons.h"
@@ -726,8 +727,13 @@ void KeyboardScreen::selectLayout(int8_t offset) {
}
getCurrentInstrumentClip()->keyboardState.currentLayout = (KeyboardLayoutType)nextLayout;
- if (getCurrentInstrumentClip()->keyboardState.currentLayout != lastLayout) {
- display->displayPopup(l10n::get(layoutList[getCurrentInstrumentClip()->keyboardState.currentLayout]->name()));
+ const char* name = l10n::get(layoutList[getCurrentInstrumentClip()->keyboardState.currentLayout]->name());
+ if (naviview.useNavigationView()) {
+ naviview.textBuffer = name;
+ naviview.drawDashboard();
+ }
+ else if (getCurrentInstrumentClip()->keyboardState.currentLayout != lastLayout) {
+ display->displayPopup(name);
}
// Ensure scale mode is as expected
diff --git a/src/deluge/gui/views/arranger_view.cpp b/src/deluge/gui/views/arranger_view.cpp
index d9eb21f991..9532fcb15e 100644
--- a/src/deluge/gui/views/arranger_view.cpp
+++ b/src/deluge/gui/views/arranger_view.cpp
@@ -32,6 +32,7 @@
#include "gui/views/audio_clip_view.h"
#include "gui/views/automation_view.h"
#include "gui/views/instrument_clip_view.h"
+#include "gui/views/navigation_view.h"
#include "gui/views/session_view.h"
#include "gui/views/view.h"
#include "gui/waveform/waveform_renderer.h"
@@ -494,6 +495,9 @@ void ArrangerView::clearArrangement() {
}
uiNeedsRendering(this, 0xFFFFFFFF, 0);
+ if (naviview.useNavigationView()) {
+ naviview.drawDashboard();
+ }
}
bool ArrangerView::opened() {
@@ -2910,6 +2914,9 @@ ActionResult ArrangerView::horizontalScrollOneSquare(int32_t direction) {
}
uiNeedsRendering(this, 0xFFFFFFFF, 0);
+ if (naviview.useNavigationView()) {
+ naviview.drawDashboard();
+ }
reassessWhetherDoingAutoScroll();
}
@@ -3136,6 +3143,9 @@ void ArrangerView::graphicsRoutine() {
if (currentUIMode != UI_MODE_HORIZONTAL_ZOOM) {
uiNeedsRendering(ui, 0xFFFFFFFF, 0);
}
+ if (naviview.useNavigationView()) {
+ naviview.drawDashboard();
+ }
}
}
@@ -3174,6 +3184,10 @@ void ArrangerView::graphicsRoutine() {
PadLEDs::setTickSquares(tickSquares, colours);
lastTickSquare = newTickSquare;
+
+ if (naviview.useNavigationView()) {
+ naviview.drawDashboard();
+ }
}
}
@@ -3231,6 +3245,9 @@ void ArrangerView::autoScrollOnPlaybackEnd() {
sharpJump:
currentSong->xScroll[NAVIGATION_ARRANGEMENT] = newScrollPos;
uiNeedsRendering(this, 0xFFFFFFFF, 0);
+ if (naviview.useNavigationView()) {
+ naviview.drawDashboard();
+ }
}
}
}
diff --git a/src/deluge/gui/views/automation_view.cpp b/src/deluge/gui/views/automation_view.cpp
index 04b33f9c9f..da5a6412a3 100644
--- a/src/deluge/gui/views/automation_view.cpp
+++ b/src/deluge/gui/views/automation_view.cpp
@@ -37,6 +37,7 @@
#include "gui/views/arranger_view.h"
#include "gui/views/audio_clip_view.h"
#include "gui/views/instrument_clip_view.h"
+#include "gui/views/navigation_view.h"
#include "gui/views/session_view.h"
#include "gui/views/timeline_view.h"
#include "gui/views/view.h"
@@ -1276,6 +1277,12 @@ void AutomationView::renderAutomationOverviewDisplayOLED(deluge::hid::display::o
deluge::hid::display::OLED::drawPermanentPopupLookingText(overviewText);
}
else {
+ if (naviview.useNavigationView()) {
+ naviview.drawDashboard();
+ naviview.drawMainboard();
+ naviview.drawBaseboard();
+ return;
+ }
overviewText = l10n::get(l10n::String::STRING_FOR_AUTOMATION_OVERVIEW);
canvas.drawStringCentred(overviewText, yPos, kTextSpacingX, kTextSpacingY);
}
@@ -1293,7 +1300,15 @@ void AutomationView::renderAutomationEditorDisplayOLED(deluge::hid::display::ole
#else
int32_t yPos = OLED_MAIN_TOPMOST_PIXEL + 3;
#endif
- canvas.drawStringCentredShrinkIfNecessary(parameterName.c_str(), yPos, kTextSpacingX, kTextSpacingY);
+ if (naviview.useNavigationView()) {
+ naviview.parameterName.clear();
+ naviview.parameterName.append(parameterName.c_str());
+ naviview.knobPosLeft = knobPosLeft;
+ naviview.knobPosRight = knobPosRight;
+ }
+ else {
+ canvas.drawStringCentredShrinkIfNecessary(parameterName.c_str(), yPos, kTextSpacingX, kTextSpacingY);
+ }
// display automation status
yPos = yPos + 12;
@@ -1326,12 +1341,22 @@ void AutomationView::renderAutomationEditorDisplayOLED(deluge::hid::display::ole
}
}
- canvas.drawStringCentred(isAutomated, yPos, kTextSpacingX, kTextSpacingY);
+ if (naviview.useNavigationView()) {
+ naviview.isAutomated = isAutomated;
+ naviview.drawDashboard();
+ naviview.drawMainboard();
+ }
+ else {
+ canvas.drawStringCentred(isAutomated, yPos, kTextSpacingX, kTextSpacingY);
+ }
// display parameter value
yPos = yPos + 12;
- if (knobPosRight != kNoSelection) {
+ if (naviview.useNavigationView()) {
+ naviview.drawBaseboard();
+ }
+ else if (knobPosRight != kNoSelection) {
char bufferLeft[10];
bufferLeft[0] = 'L';
bufferLeft[1] = ':';
@@ -1367,7 +1392,16 @@ void AutomationView::renderNoteEditorDisplayOLED(deluge::hid::display::oled_canv
#else
int32_t yPos = OLED_MAIN_TOPMOST_PIXEL + 3;
#endif
- canvas.drawStringCentredShrinkIfNecessary(parameterName.c_str(), yPos, kTextSpacingX, kTextSpacingY);
+ if (naviview.useNavigationView()) {
+ naviview.parameterName.clear();
+ naviview.parameterName.append(parameterName.c_str());
+ naviview.knobPosLeft = knobPosLeft;
+ naviview.knobPosRight = knobPosRight;
+ naviview.drawDashboard();
+ }
+ else {
+ canvas.drawStringCentredShrinkIfNecessary(parameterName.c_str(), yPos, kTextSpacingX, kTextSpacingY);
+ }
// display note / drum name
yPos = yPos + 12;
@@ -1407,12 +1441,21 @@ void AutomationView::renderNoteEditorDisplayOLED(deluge::hid::display::oled_canv
}
}
- canvas.drawStringCentred(noteRowName, yPos, kTextSpacingX, kTextSpacingY);
+ if (naviview.useNavigationView()) {
+ strncpy(naviview.noteRowName, noteRowName, 49);
+ naviview.drawMainboard();
+ }
+ else {
+ canvas.drawStringCentred(noteRowName, yPos, kTextSpacingX, kTextSpacingY);
+ }
// display parameter value
yPos = yPos + 12;
- if (automationParamType == AutomationParamType::NOTE_VELOCITY) {
+ if (naviview.useNavigationView()) {
+ naviview.drawBaseboard();
+ }
+ else if (automationParamType == AutomationParamType::NOTE_VELOCITY) {
if (knobPosRight != kNoSelection) {
char bufferLeft[10];
bufferLeft[0] = 'L';
diff --git a/src/deluge/gui/views/navigation_view.cpp b/src/deluge/gui/views/navigation_view.cpp
new file mode 100644
index 0000000000..ec44ba537a
--- /dev/null
+++ b/src/deluge/gui/views/navigation_view.cpp
@@ -0,0 +1,504 @@
+/*
+ * Copyright © 2014-2023 Synthstrom Audible Limited
+ *
+ * This file is part of The Synthstrom Audible Deluge Firmware.
+ *
+ * The Synthstrom Audible Deluge Firmware 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 "gui/views/navigation_view.h"
+#include "definitions_cxx.hpp"
+#include "extern.h"
+#include "gui/l10n/l10n.h"
+#include "gui/ui/root_ui.h"
+#include "gui/ui/ui.h"
+#include "gui/views/arranger_view.h"
+#include "gui/views/automation_view.h"
+#include "gui/views/performance_view.h"
+#include "gui/views/session_view.h"
+#include "hid/display/display.h"
+#include "hid/display/oled.h"
+#include "io/midi/midi_engine.h"
+#include "lib/printf.h"
+#include "model/clip/clip.h"
+#include "model/instrument/cv_instrument.h"
+#include "model/instrument/instrument.h"
+#include "model/instrument/kit.h"
+#include "model/instrument/melodic_instrument.h"
+#include "model/instrument/midi_instrument.h"
+#include "model/song/song.h"
+#include "navigation_view.h"
+#include "playback/mode/session.h"
+#include "processing/audio_output.h"
+#include "util/d_string.h"
+
+using namespace deluge;
+using namespace gui;
+
+NavigationView naviview{};
+
+NavigationView::NavigationView() {
+ knobPosLeft = 0;
+ knobPosRight = 0;
+}
+
+// Returns true when In-Key Keyboard is set
+// as default and the Deluge has an OLED display.
+bool NavigationView::useNavigationView() {
+ return (display->haveOLED() && FlashStorage::defaultKeyboardLayout == KeyboardLayoutType::KeyboardLayoutTypeInKey);
+}
+
+// Top Line Dashboard on the OLED screen
+void NavigationView::drawDashboard() {
+ DEF_STACK_STRING_BUF(info, 25);
+ char buffer[25];
+ const char* name;
+
+ hasTempoBPM = false;
+ hasScale = false;
+ hasRemainingCountdown = false;
+ RootUI* rootUI = getRootUI();
+ UIType rootUIType = rootUI->getUIType();
+ bool isSessionView = (rootUI == &sessionView);
+ bool isPerformanceView = (rootUI == &performanceView);
+ bool isArrangerView = (rootUI == &arrangerView);
+ bool isAudioClipView = (rootUI == &audioClipView);
+ bool isAutomationView = (rootUI == &automationView);
+ bool isAutomationOverview = (isAutomationView && ((AutomationView*)rootUI)->onAutomationOverview());
+ bool isKeyboardView = (rootUIType == UIType::KEYBOARD_SCREEN);
+ Clip* clip = getCurrentClip();
+ Output* output = getCurrentOutput();
+ OutputType outputType = output->type;
+ bool isKit = outputType == OutputType::KIT;
+
+ int32_t channel = -1;
+ switch (outputType) {
+ case OutputType::MIDI_OUT:
+ case OutputType::CV:
+ channel = ((NonAudioInstrument*)output)->getChannel();
+ break;
+ case OutputType::AUDIO:
+ channel = static_cast(((AudioOutput*)output)->mode);
+ break;
+ default:
+ break;
+ }
+ const char* outputTypeText = getOutputTypeName(outputType, channel);
+
+ int32_t root = currentSong->key.rootNote;
+ Scale scale = currentSong->getCurrentScale();
+
+ int32_t navSysId;
+ uint32_t maxLength{0};
+
+ if (isArrangerView) {
+ navSysId = NAVIGATION_ARRANGEMENT;
+ }
+ else {
+ navSysId = NAVIGATION_CLIP;
+ }
+
+ uint32_t xScroll = currentSong->xScroll[navSysId];
+ uint32_t noteLength = currentSong->xZoom[navSysId];
+ if (isArrangerView) {
+ maxLength = ((ArrangerView*)rootUI)->getMaxLength();
+ }
+ else if (isSessionView) {
+ maxLength = ((SessionView*)rootUI)->getMaxLength();
+ }
+ else if (clip) {
+ maxLength = clip->getMaxLength();
+ }
+
+ int32_t magnitude = getNoteMagnitudeFfromNoteLength(noteLength, currentSong->getInputTickMagnitude());
+ // Positive magnitudes are bars, negative magnitudes are divisions of bars.
+ uint32_t numBars = (uint32_t)1 << magnitude;
+ uint32_t division = (uint32_t)1 << (0 - magnitude);
+
+ // Get Number of Bars and Beats
+ uint32_t oneBar = currentSong->getBarLength();
+ uint32_t totalBarsInClip = maxLength / oneBar;
+ uint32_t whichBar = xScroll / oneBar;
+ uint32_t posWithinBar = xScroll - whichBar * oneBar;
+ uint32_t whichBeat = posWithinBar / (oneBar >> 2);
+ uint32_t posWithinBeat = posWithinBar - whichBeat * (oneBar >> 2);
+ uint32_t whichSubBeat = posWithinBeat / (oneBar >> 4);
+ whichBar++;
+ whichBeat++;
+ whichSubBeat++;
+
+ if (totalBarsInClip < whichBar) { // 1/0 or 3/2 appear when the last measure is less than 4 beats.
+ totalBarsInClip = whichBar;
+ }
+
+ if (isArrangerView && magnitude < 0 && division >= 8) {
+ sprintf(buffer, "%d/%d:%d:%d", whichBar, totalBarsInClip, whichBeat, whichSubBeat);
+ }
+ else if (isArrangerView && magnitude < 0 && division >= 2) {
+ sprintf(buffer, "%d/%d:%d", whichBar, totalBarsInClip, whichBeat);
+ }
+ else if (magnitude < 0 && division == 128) {
+ sprintf(buffer, "%d/%d:%d:%d", whichBar, totalBarsInClip, whichBeat, whichSubBeat);
+ }
+ else if (magnitude < 0 && division >= 32) {
+ sprintf(buffer, "%d/%d:%d", whichBar, totalBarsInClip, whichBeat);
+ }
+ else {
+ sprintf(buffer, "%d/%d", whichBar, totalBarsInClip);
+ }
+
+ if (isKeyboardView || isPerformanceView || isAutomationOverview) {
+ // do nothing, navigation is not available, label added below
+ }
+ else {
+ info.append(buffer);
+ }
+
+ // Get Subdivision
+ if (magnitude < 0) {
+ sprintf(buffer, " 1/%d", division);
+ }
+ else {
+ sprintf(buffer, " %dB/C", numBars);
+ }
+ if (isKeyboardView || isPerformanceView || isAutomationOverview) {
+ // do nothing, navigation is not available, label added below
+ }
+ else {
+ info.append(buffer);
+ }
+
+ if (isPerformanceView) {
+ name = l10n::get(l10n::String::STRING_FOR_PERFORM_VIEW);
+ info.append(name);
+ }
+ else if (isAutomationOverview) {
+ name = l10n::get(l10n::String::STRING_FOR_AUTOMATION_OVERVIEW);
+ info.append(name);
+ }
+ else if (isKeyboardView) {
+ info.append(textBuffer);
+ }
+ else if (isAutomationView) {
+ if (strcmp(isAutomated, l10n::get(l10n::String::STRING_FOR_AUTOMATION_ON)) == 0) {
+ info.append(" "); // Text added after navigation
+ info.append(isAutomated);
+ }
+ }
+ else if (isAudioClipView) {
+ info.append(" "); // text added after navigation
+ info.append(outputTypeText + 6); // Skip 'Audio '
+ }
+ else if (!isKit) { // Key and Scale added after navigation
+ hasScale = true;
+ // Get Key and Scale
+ clearBuffer(buffer, 25);
+ int32_t isNatural = 1; // gets modified inside noteCodeToString to be 0 if sharp.
+ noteCodeToString(root, buffer, &isNatural, false);
+ // noteCodeToString(root, buffer, false, root, scale);
+ info.append(" ");
+ info.append(buffer);
+ NoteSet notes = currentSong->key.modeNotes;
+ bool moreMajor = (notes.majorness() >= 0);
+ if (!moreMajor) {
+ info.append("-");
+ }
+ switch (scale) {
+ case MAJOR_SCALE:
+ break;
+ case MINOR_SCALE:
+ info.append(" vi");
+ break;
+ case DORIAN_SCALE:
+ info.append(" ii");
+ break;
+ case PHRYGIAN_SCALE:
+ info.append(" iii");
+ break;
+ case LYDIAN_SCALE:
+ info.append(" IV");
+ break;
+ case MIXOLYDIAN_SCALE:
+ info.append(" V");
+ break;
+ case LOCRIAN_SCALE:
+ info.append(" vii");
+ break;
+ default:
+ sprintf(buffer, " %s", getScaleName(scale));
+ for (int i = 1; i < 25 - 1; ++i) {
+ if (buffer[i] == 0)
+ break;
+ if (buffer[i] == ' ' && buffer[i + 1] == 'M') { // Strip off ' Minor'
+ buffer[i] = 0;
+ break;
+ }
+ }
+ info.append(buffer);
+ }
+ }
+
+#if OLED_MAIN_HEIGHT_PIXELS == 64
+ int32_t yPos = OLED_MAIN_TOPMOST_PIXEL + 12;
+#else
+ int32_t yPos = OLED_MAIN_TOPMOST_PIXEL + 3;
+#endif
+ hid::display::oled_canvas::Canvas& canvas = hid::display::OLED::main;
+ canvas.clearAreaExact(0, yPos, OLED_MAIN_WIDTH_PIXELS - 1, yPos + kTextSpacingY);
+ canvas.drawString(info.data(), 0, yPos, kTextSpacingX, kTextSpacingY);
+ if (!isAudioClipView && !isAutomationView
+ && (isSessionView || isArrangerView || isPerformanceView || scale <= LOCRIAN_SCALE)) {
+ drawTempoBPM();
+ }
+ hid::display::OLED::markChanged();
+}
+
+// Middle Line of OLED screen
+void NavigationView::drawMainboard(const char* nameToDraw) {
+ DEF_STACK_STRING_BUF(info, 25);
+ char buffer[12];
+ const char* name;
+ RootUI* rootUI = getRootUI();
+ UIType rootUIType = rootUI->getUIType();
+ Clip* clip = getCurrentClip();
+ Output* output = getCurrentOutput();
+ OutputType outputType = output->type;
+ Instrument* instrument = (Instrument*)output;
+
+ int32_t channel{0};
+ int32_t channelSuffix{0};
+ switch (outputType) {
+ case OutputType::MIDI_OUT:
+ channelSuffix = ((MIDIInstrument*)instrument)->channelSuffix;
+ // no break;
+ case OutputType::CV:
+ channel = ((NonAudioInstrument*)output)->getChannel();
+ break;
+ case OutputType::AUDIO:
+ channel = static_cast(((AudioOutput*)output)->mode);
+ break;
+ default:
+ break;
+ }
+ const char* outputTypeText = getOutputTypeName(outputType, channel);
+
+ switch (rootUIType) {
+ case UIType::INSTRUMENT_CLIP:
+ case UIType::AUDIO_CLIP:
+ case UIType::AUDIO_RECORDER:
+ case UIType::AUTOMATION:
+ case UIType::KEYBOARD_SCREEN:
+ if (outputType == OutputType::MIDI_OUT) {
+ if (strncmp(outputTypeText, "Int", 3) == 0) {
+ name = "Int.";
+ }
+ else {
+ name = outputTypeText;
+ }
+ }
+ else if (outputType == OutputType::CV) {
+ name = "CV";
+ }
+ else {
+ if (nameToDraw != nullptr) { // current output is old, load_instrument_preset_ui
+ // calls drawOutputNameFromDetails before loading is complete
+ // so use the parameter instead.
+ name = nameToDraw;
+ }
+ else {
+ name = getCurrentOutput()->name.get();
+ }
+ }
+ info.append(name);
+ break;
+ case UIType::SESSION:
+ case UIType::ARRANGER:
+ case UIType::PERFORMANCE:
+ case UIType::LOAD_SONG:
+ case UIType::NONE:
+ if (nameToDraw != nullptr) { // current output is old, load_instrument_preset_ui
+ // calls drawOutputNameFromDetails before loading is complete
+ // so use the parameter instead.
+ name = nameToDraw;
+ }
+ else if (currentSong->name.isEmpty()) {
+ name = "UNSAVED";
+ }
+ else {
+ name = currentSong->name.get();
+ }
+ info.append(name);
+ break;
+ default:
+ return; // nothing to draw
+ }
+
+ if (outputType == OutputType::MIDI_OUT) {
+ info.append(" ");
+ if (channel < 16) {
+ slotToString(channel + 1, channelSuffix, buffer, 1);
+ info.append(buffer);
+ }
+ else if (channel == MIDI_CHANNEL_MPE_LOWER_ZONE || channel == MIDI_CHANNEL_MPE_UPPER_ZONE) {
+ info.append((channel == MIDI_CHANNEL_MPE_LOWER_ZONE) ? "Lower" : "Upper");
+ }
+ else {
+ info.append("Transpose");
+ }
+ }
+ if (outputType == OutputType::CV) {
+ info.append(" ");
+ if (channel < both) {
+ info.appendInt(channel + 1);
+ }
+ else {
+ info.append("1 and 2");
+ }
+ }
+
+ deluge::hid::display::oled_canvas::Canvas& canvas = hid::display::OLED::main;
+#if OLED_MAIN_HEIGHT_PIXELS == 64
+ int32_t yPos = OLED_MAIN_TOPMOST_PIXEL + 30;
+#else
+ int32_t yPos = OLED_MAIN_TOPMOST_PIXEL + 17;
+#endif
+ canvas.clearAreaExact(0, yPos, OLED_MAIN_WIDTH_PIXELS - 1, yPos + kTextSpacingY);
+ int32_t stringLengthPixels = canvas.getStringWidthInPixels(name, kTextTitleSizeY);
+ if (stringLengthPixels <= OLED_MAIN_WIDTH_PIXELS) {
+ canvas.drawStringCentred(info.data(), yPos, kTextTitleSpacingX, kTextTitleSizeY);
+ }
+ else {
+ canvas.drawString(info.data(), 0, yPos, kTextTitleSpacingX, kTextTitleSizeY);
+ deluge::hid::display::OLED::setupSideScroller(0, name, 0, OLED_MAIN_WIDTH_PIXELS, yPos, yPos + kTextTitleSizeY,
+ kTextTitleSpacingX, kTextTitleSizeY, false);
+ }
+ hid::display::OLED::markChanged();
+}
+
+// Bottom Line of OLED screen
+void NavigationView::drawBaseboard() {
+ DEF_STACK_STRING_BUF(info, 25);
+ char const* baseText;
+ RootUI* rootUI = getRootUI();
+ UIType rootUIType = rootUI->getUIType();
+ Clip* clip = getCurrentClip();
+ Output* output = getCurrentOutput();
+ OutputType outputType = output->type;
+
+ switch (rootUIType) {
+ case UIType::PERFORMANCE:
+ if (!parameterName.empty()) {
+ info.append(parameterName.data());
+ info.append(":");
+ if (textBuffer) {
+ info.append(textBuffer);
+ }
+ else {
+ info.appendInt(knobPosLeft);
+ }
+ }
+ break;
+ case UIType::AUTOMATION:
+ if (((AutomationView*)rootUI)->inAutomationEditor()) {
+ // display parameter name
+ info.append(parameterName.data());
+ if (knobPosRight != kNoSelection) {
+ info.append(":L");
+ info.appendInt(knobPosLeft);
+ info.append("-R");
+ info.appendInt(knobPosRight);
+ }
+ else {
+ info.append(":");
+ info.appendInt(knobPosLeft);
+ }
+ }
+ else if (((AutomationView*)rootUI)->inNoteEditor()) {
+ if (((AutomationView*)rootUI)->automationParamType == AutomationParamType::NOTE_VELOCITY) {
+ info.append("Velocity: ");
+ info.append(noteRowName);
+ }
+ }
+ break;
+ case UIType::INSTRUMENT_CLIP:
+ case UIType::AUDIO_CLIP:
+ case UIType::AUDIO_RECORDER:
+ case UIType::KEYBOARD_SCREEN:
+ if (clip != nullptr) {
+ if (clip->name.isEmpty()) {
+ info.append("Section ");
+ info.appendInt(clip->section + 1);
+ }
+ else {
+ info.appendInt(clip->section + 1);
+ info.append(": ");
+ info.append(clip->name.get());
+ }
+ }
+ break;
+ case UIType::SESSION:
+ case UIType::ARRANGER:
+ case UIType::LOAD_SONG:
+ case UIType::NONE:
+ default:
+ break;
+ }
+
+ int32_t yPos = OLED_MAIN_TOPMOST_PIXEL + 32;
+ hid::display::oled_canvas::Canvas& canvas = hid::display::OLED::main;
+ canvas.clearAreaExact(0, yPos, OLED_MAIN_WIDTH_PIXELS - 1, yPos + kTextSpacingY);
+ canvas.drawString(info.data(), 0, yPos, kTextSpacingX, kTextSpacingY);
+ deluge::hid::display::OLED::setupSideScroller(1, info.data(), 0, OLED_MAIN_WIDTH_PIXELS, yPos, yPos + kTextSpacingY,
+ kTextSpacingX, kTextSpacingY, false);
+ hid::display::OLED::markChanged();
+}
+
+void NavigationView::drawRemainingCountdown() {
+ hasRemainingCountdown = true;
+ int32_t sixteenthNotesRemaining = session.getNumSixteenthNotesRemainingTilLaunch();
+ int32_t barsRemaining = ((sixteenthNotesRemaining - 1) / 16) + 1;
+ int32_t quarterNotesRemaining = ((sixteenthNotesRemaining - 1) / 4) + 1;
+ int32_t rndQuarterNotesRemaining = ((quarterNotesRemaining - 1) % 4) + 1;
+
+ DEF_STACK_STRING_BUF(buffer, 10);
+ buffer.append(" Q");
+ buffer.appendInt(barsRemaining);
+ buffer.append(":");
+ buffer.appendInt(rndQuarterNotesRemaining);
+
+ int32_t yPos = OLED_MAIN_TOPMOST_PIXEL + 3;
+ hid::display::oled_canvas::Canvas& canvas = hid::display::OLED::main;
+
+ canvas.clearAreaExact(OLED_MAIN_WIDTH_PIXELS - (kTextSpacingX * buffer.length() + 1), yPos,
+ OLED_MAIN_WIDTH_PIXELS - 1, yPos + kTextSpacingY);
+ canvas.drawStringAlignRight(buffer.data(), yPos, kTextSpacingX, kTextSpacingY);
+ hid::display::OLED::markChanged();
+}
+
+void NavigationView::drawTempoBPM() {
+ hasTempoBPM = true;
+ DEF_STACK_STRING_BUF(tempoBPM, 10);
+ sessionView.lastDisplayedTempo = playbackHandler.calculateBPM(playbackHandler.getTimePerInternalTickFloat());
+ playbackHandler.getTempoStringForOLED(sessionView.lastDisplayedTempo, tempoBPM);
+ hid::display::oled_canvas::Canvas& canvas = hid::display::OLED::main;
+ sessionView.displayTempoBPM(canvas, tempoBPM, true);
+ deluge::hid::display::OLED::markChanged();
+}
+
+void NavigationView::clearBuffer(char* buffer, int8_t bufferLength) {
+ int8_t i;
+ for (i = 0; i < bufferLength; ++i) {
+ if (buffer[i] != 0) {
+ buffer[i] = 0;
+ }
+ }
+}
diff --git a/src/deluge/gui/views/navigation_view.h b/src/deluge/gui/views/navigation_view.h
new file mode 100644
index 0000000000..80c74d3d50
--- /dev/null
+++ b/src/deluge/gui/views/navigation_view.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright © 2014-2023 Synthstrom Audible Limited
+ *
+ * This file is part of The Synthstrom Audible Deluge Firmware.
+ *
+ * The Synthstrom Audible Deluge Firmware 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 .
+ */
+
+#pragma once
+
+#include "definitions_cxx.hpp"
+#include "util/d_string.h"
+#include
+
+class InstrumentClip;
+class AudioClip;
+class UI;
+class RootUI;
+class Output;
+
+// Active view of current state of the Deluge
+class NavigationView {
+public:
+ NavigationView();
+
+ // AutomationView parameters
+ int32_t knobPosLeft;
+ int32_t knobPosRight;
+ const char* isAutomated;
+ DEF_STACK_STRING_BUF(parameterName, 25);
+ char noteRowName[50]; // Used by VELOCITY automation
+
+ const char* textBuffer; // Used by KEYBOARD and PERFORMANCE as label to replace navigation
+ bool hasTempoBPM; // True when BPM is drawn on dashboard
+ bool hasScale; // True when Scale is drawn on dashboard
+ bool hasRemainingCountdown; // True when cue overlays the tempo BPM
+
+ bool useNavigationView();
+ void drawDashboard();
+ void drawMainboard(const char* nameToDraw = nullptr);
+ void drawBaseboard();
+ void drawRemainingCountdown();
+ void drawTempoBPM();
+
+private:
+ void clearBuffer(char* buffer, int8_t bufferLength = 0);
+ const char* retrieveOutputTypeName(Output* output);
+};
+
+extern NavigationView naviview;
diff --git a/src/deluge/gui/views/performance_view.cpp b/src/deluge/gui/views/performance_view.cpp
index 0101a3ada2..f436ea4988 100644
--- a/src/deluge/gui/views/performance_view.cpp
+++ b/src/deluge/gui/views/performance_view.cpp
@@ -27,6 +27,7 @@
#include "gui/views/arranger_view.h"
#include "gui/views/automation_view.h"
#include "gui/views/instrument_clip_view.h"
+#include "gui/views/navigation_view.h"
#include "gui/views/session_view.h"
#include "gui/views/view.h"
#include "hid/button.h"
@@ -535,10 +536,16 @@ void PerformanceView::renderViewDisplay() {
yPos = yPos + 12;
- // Render "Performance View" in the middle of the OLED screen
- image.drawStringCentred(l10n::get(l10n::String::STRING_FOR_PERFORM_VIEW), yPos, kTextSpacingX,
- kTextSpacingY);
-
+ if (naviview.useNavigationView()) {
+ naviview.drawDashboard();
+ naviview.drawMainboard();
+ naviview.drawBaseboard();
+ }
+ else {
+ // Render "Performance View" in the middle of the OLED screen
+ image.drawStringCentred(l10n::get(l10n::String::STRING_FOR_PERFORM_VIEW), yPos, kTextSpacingX,
+ kTextSpacingY);
+ }
deluge::hid::display::OLED::markChanged();
}
else {
@@ -565,7 +572,16 @@ void PerformanceView::renderFXDisplay(params::Kind paramKind, int32_t paramID, i
#endif
yPos = yPos + 12;
- image.drawStringCentred(parameterName, yPos, kTextSpacingX, kTextSpacingY);
+ if (naviview.useNavigationView()) {
+ naviview.parameterName.clear();
+ naviview.parameterName.append(parameterName);
+ naviview.drawDashboard();
+ naviview.drawMainboard();
+ naviview.drawBaseboard();
+ }
+ else {
+ image.drawStringCentred(parameterName, yPos, kTextSpacingX, kTextSpacingY);
+ }
deluge::hid::display::OLED::markChanged();
}
@@ -587,7 +603,16 @@ void PerformanceView::renderFXDisplay(params::Kind paramKind, int32_t paramID, i
#else
int32_t yPos = OLED_MAIN_TOPMOST_PIXEL + 3;
#endif
- image.drawStringCentred(parameterName, yPos, kTextSpacingX, kTextSpacingY);
+ if (naviview.useNavigationView()) {
+ naviview.parameterName.clear();
+ naviview.parameterName.append(parameterName);
+ naviview.drawDashboard();
+ naviview.drawMainboard();
+ naviview.drawBaseboard();
+ }
+ else {
+ image.drawStringCentred(parameterName, yPos, kTextSpacingX, kTextSpacingY);
+ }
// display parameter value
yPos = yPos + 24;
@@ -610,12 +635,28 @@ void PerformanceView::renderFXDisplay(params::Kind paramKind, int32_t paramID, i
else { // 64ths stutter: all 4 leds turned on
buffer = "64ths";
}
- image.drawStringCentred(buffer, yPos, kTextSpacingX, kTextSpacingY);
+ if (naviview.useNavigationView()) {
+ naviview.knobPosLeft = kNoSelection;
+ naviview.knobPosRight = kNoSelection;
+ naviview.textBuffer = buffer;
+ naviview.drawBaseboard();
+ }
+ else {
+ image.drawStringCentred(buffer, yPos, kTextSpacingX, kTextSpacingY);
+ }
}
else {
- char buffer[5];
- intToString(knobPos, buffer);
- image.drawStringCentred(buffer, yPos, kTextSpacingX, kTextSpacingY);
+ if (naviview.useNavigationView()) {
+ naviview.knobPosLeft = knobPos;
+ naviview.knobPosRight = kNoSelection;
+ naviview.textBuffer = nullptr;
+ naviview.drawBaseboard();
+ }
+ else {
+ char buffer[5];
+ intToString(knobPos, buffer);
+ image.drawStringCentred(buffer, yPos, kTextSpacingX, kTextSpacingY);
+ }
}
deluge::hid::display::OLED::markChanged();
diff --git a/src/deluge/gui/views/session_view.cpp b/src/deluge/gui/views/session_view.cpp
index c720399519..d6d926c459 100644
--- a/src/deluge/gui/views/session_view.cpp
+++ b/src/deluge/gui/views/session_view.cpp
@@ -39,6 +39,7 @@
#include "gui/views/audio_clip_view.h"
#include "gui/views/automation_view.h"
#include "gui/views/instrument_clip_view.h"
+#include "gui/views/navigation_view.h"
#include "gui/views/performance_view.h"
#include "gui/views/view.h"
#include "gui/waveform/waveform_renderer.h"
@@ -1906,8 +1907,15 @@ void SessionView::renderOLED(deluge::hid::display::oled_canvas::Canvas& canvas)
if (currentPlaybackMode == &session) {
if (session.launchEventAtSwungTickCount) {
intToString(session.numRepeatsTilLaunch, &loopsRemainingText[17]);
- deluge::hid::display::OLED::clearMainImage();
- deluge::hid::display::OLED::drawPermanentPopupLookingText(loopsRemainingText);
+ if (naviview.useNavigationView()
+ && (strncmp(loopsRemainingText, "Bars ", 5) || strncmp(loopsRemainingText, "Beats", 5)
+ || strncmp(loopsRemainingText, "Loops", 5))) {
+ naviview.drawRemainingCountdown();
+ }
+ else {
+ deluge::hid::display::OLED::clearMainImage();
+ deluge::hid::display::OLED::drawPermanentPopupLookingText(loopsRemainingText);
+ }
}
}
@@ -2028,11 +2036,15 @@ void SessionView::renderViewDisplay() {
#else
int32_t yPos = OLED_MAIN_TOPMOST_PIXEL + 3;
#endif
-
- DEF_STACK_STRING_BUF(tempoBPM, 10);
- lastDisplayedTempo = playbackHandler.calculateBPM(playbackHandler.getTimePerInternalTickFloat());
- playbackHandler.getTempoStringForOLED(lastDisplayedTempo, tempoBPM);
- displayTempoBPM(canvas, tempoBPM, false);
+ if (naviview.useNavigationView()) {
+ naviview.drawDashboard();
+ }
+ else {
+ DEF_STACK_STRING_BUF(tempoBPM, 10);
+ lastDisplayedTempo = playbackHandler.calculateBPM(playbackHandler.getTimePerInternalTickFloat());
+ playbackHandler.getTempoStringForOLED(lastDisplayedTempo, tempoBPM);
+ displayTempoBPM(canvas, tempoBPM, false);
+ }
#if OLED_MAIN_HEIGHT_PIXELS == 64
yPos = OLED_MAIN_TOPMOST_PIXEL + 30;
@@ -2050,7 +2062,10 @@ void SessionView::renderViewDisplay() {
int32_t stringLengthPixels = canvas.getStringWidthInPixels(name, kTextTitleSizeY);
- if (stringLengthPixels <= OLED_MAIN_WIDTH_PIXELS) {
+ if (naviview.useNavigationView()) {
+ naviview.drawMainboard();
+ }
+ else if (stringLengthPixels <= OLED_MAIN_WIDTH_PIXELS) {
canvas.drawStringCentred(name, yPos, kTextTitleSpacingX, kTextTitleSizeY);
}
else {
@@ -2061,9 +2076,14 @@ void SessionView::renderViewDisplay() {
yPos = OLED_MAIN_TOPMOST_PIXEL + 32;
- DEF_STACK_STRING_BUF(rootNoteAndScaleName, 40);
- currentSong->getCurrentRootNoteAndScaleName(rootNoteAndScaleName);
- displayCurrentRootNoteAndScaleName(canvas, rootNoteAndScaleName, false);
+ if (naviview.useNavigationView()) {
+ naviview.drawBaseboard();
+ }
+ else {
+ DEF_STACK_STRING_BUF(rootNoteAndScaleName, 40);
+ currentSong->getCurrentRootNoteAndScaleName(rootNoteAndScaleName);
+ displayCurrentRootNoteAndScaleName(canvas, rootNoteAndScaleName, false);
+ }
deluge::hid::display::OLED::markChanged();
}
@@ -2075,7 +2095,8 @@ void SessionView::displayTempoBPM(deluge::hid::display::oled_canvas::Canvas& can
int32_t metronomeIconSpacingX = 7 + 3;
if (clearArea) {
- canvas.clearAreaExact(OLED_MAIN_WIDTH_PIXELS - (kTextSpacingX * 6) - metronomeIconSpacingX,
+ canvas.clearAreaExact(OLED_MAIN_WIDTH_PIXELS - (kTextSpacingX * (tempoBPM.length() + 1))
+ - metronomeIconSpacingX,
OLED_MAIN_TOPMOST_PIXEL, OLED_MAIN_WIDTH_PIXELS - 1, yPos + kTextSpacingY);
}
@@ -2382,7 +2403,13 @@ int32_t SessionView::displayLoopsRemainingPopup(bool ephemeral) {
}
popupMsg.appendInt(quarterNotesRemaining);
}
- if (display->haveOLED() && !ephemeral) {
+
+ if (naviview.useNavigationView()
+ && (strncmp(popupMsg.c_str(), "Bars ", 5) || strncmp(popupMsg.c_str(), "Beats", 5)
+ || strncmp(popupMsg.c_str(), "Loops", 5))) {
+ naviview.drawRemainingCountdown();
+ }
+ else if (display->haveOLED() && !ephemeral) {
deluge::hid::display::OLED::clearMainImage();
deluge::hid::display::OLED::drawPermanentPopupLookingText(popupMsg.c_str());
deluge::hid::display::OLED::sendMainImage();
diff --git a/src/deluge/gui/views/timeline_view.cpp b/src/deluge/gui/views/timeline_view.cpp
index 7cab2decbc..0f4580f553 100644
--- a/src/deluge/gui/views/timeline_view.cpp
+++ b/src/deluge/gui/views/timeline_view.cpp
@@ -18,6 +18,7 @@
#include "gui/views/timeline_view.h"
#include "definitions_cxx.hpp"
#include "extern.h"
+#include "gui/views/navigation_view.h"
#include "gui/views/view.h"
#include "hid/button.h"
#include "hid/buttons.h"
@@ -36,6 +37,9 @@ void TimelineView::scrollFinished() {
// Needed because sometimes we initiate a scroll before reverting an Action, so we need to
// properly render again afterwards
uiNeedsRendering(getRootUI(), 0xFFFFFFFF, 0);
+ if (naviview.useNavigationView()) {
+ naviview.drawDashboard();
+ }
}
// Virtual function
@@ -118,10 +122,16 @@ ActionResult TimelineView::buttonAction(deluge::hid::Button b, bool on, bool inC
}
void TimelineView::displayZoomLevel(bool justPopup) {
- DEF_STACK_STRING_BUF(text, 30);
- currentSong->getNoteLengthName(text, currentSong->xZoom[getNavSysId()], "-notes", true);
- display->displayPopup(text.data(), justPopup ? 3 : 0, true);
+ if (naviview.useNavigationView()) {
+ naviview.drawDashboard();
+ }
+ else {
+ DEF_STACK_STRING_BUF(text, 30);
+ currentSong->getNoteLengthName(text, currentSong->xZoom[getNavSysId()], "-notes", true);
+
+ display->displayPopup(text.data(), justPopup ? 3 : 0, true);
+ }
}
bool horizontalEncoderActionLock = false;
@@ -253,7 +263,10 @@ void TimelineView::displayNumberOfBarsAndBeats(uint32_t number, uint32_t quantiz
whichSubBeat++;
}
- if (display->haveOLED()) {
+ if (naviview.useNavigationView()) {
+ naviview.drawDashboard();
+ }
+ else if (display->haveOLED()) {
char buffer[15];
sprintf(buffer, "%d : %d : %d", whichBar, whichBeat, whichSubBeat);
display->popupTextTemporary(buffer);
diff --git a/src/deluge/gui/views/view.cpp b/src/deluge/gui/views/view.cpp
index dd2dcfacfe..3b24f4c358 100644
--- a/src/deluge/gui/views/view.cpp
+++ b/src/deluge/gui/views/view.cpp
@@ -36,6 +36,7 @@
#include "gui/views/arranger_view.h"
#include "gui/views/automation_view.h"
#include "gui/views/instrument_clip_view.h"
+#include "gui/views/navigation_view.h"
#include "gui/views/performance_view.h"
#include "gui/views/session_view.h"
#include "hid/buttons.h"
@@ -2007,7 +2008,11 @@ void View::drawOutputNameFromDetails(OutputType outputType, int32_t channel, int
return;
}
- if (display->haveOLED()) {
+ if (naviview.useNavigationView()) {
+ hid::display::OLED::clearMainImage();
+ naviview.drawDashboard();
+ }
+ else if (display->haveOLED()) {
deluge::hid::display::oled_canvas::Canvas& canvas = hid::display::OLED::main;
hid::display::OLED::clearMainImage();
@@ -2037,7 +2042,10 @@ void View::drawOutputNameFromDetails(OutputType outputType, int32_t channel, int
int32_t stringLengthPixels = canvas.getStringWidthInPixels(nameToDraw, kTextTitleSizeY);
- if (stringLengthPixels <= OLED_MAIN_WIDTH_PIXELS) {
+ if (naviview.useNavigationView()) {
+ naviview.drawMainboard(nameToDraw);
+ }
+ else if (stringLengthPixels <= OLED_MAIN_WIDTH_PIXELS) {
canvas.drawStringCentred(nameToDraw, yPos, kTextTitleSpacingX, kTextTitleSizeY);
}
else {
@@ -2047,7 +2055,10 @@ void View::drawOutputNameFromDetails(OutputType outputType, int32_t channel, int
kTextTitleSizeY, false);
}
- if (clip) {
+ if (naviview.useNavigationView()) {
+ naviview.drawBaseboard();
+ }
+ else if (clip) {
// "SECTION NN" is 10, "NN: " is 3 => 10 over current name is always enough.
DEF_STACK_STRING_BUF(info, clip->name.getLength() + 10);
if (clip->name.isEmpty()) {
diff --git a/src/deluge/model/clip/instrument_clip_minder.cpp b/src/deluge/model/clip/instrument_clip_minder.cpp
index 4d6728f73e..243d17e0c9 100644
--- a/src/deluge/model/clip/instrument_clip_minder.cpp
+++ b/src/deluge/model/clip/instrument_clip_minder.cpp
@@ -29,6 +29,7 @@
#include "gui/views/arranger_view.h"
#include "gui/views/automation_view.h"
#include "gui/views/instrument_clip_view.h"
+#include "gui/views/navigation_view.h"
#include "gui/views/view.h"
#include "hid/button.h"
#include "hid/buttons.h"
@@ -589,6 +590,10 @@ bool InstrumentClipMinder::setScale(Scale newScale) {
}
void InstrumentClipMinder::displayScaleName(Scale scale) {
+ if (naviview.useNavigationView() && naviview.hasScale) {
+ naviview.drawDashboard();
+ return;
+ }
display->displayPopup(getScaleName(scale));
}
diff --git a/src/deluge/model/song/song.cpp b/src/deluge/model/song/song.cpp
index aa40bd456b..5949161b98 100644
--- a/src/deluge/model/song/song.cpp
+++ b/src/deluge/model/song/song.cpp
@@ -25,6 +25,7 @@
#include "gui/views/arranger_view.h"
#include "gui/views/audio_clip_view.h"
#include "gui/views/instrument_clip_view.h"
+#include "gui/views/navigation_view.h"
#include "gui/views/performance_view.h"
#include "gui/views/session_view.h"
#include "gui/views/view.h"
@@ -5799,7 +5800,11 @@ void Song::displayCurrentRootNoteAndScaleName() {
UI* currentUI = getCurrentUI();
bool isSessionView = (currentUI == &sessionView || currentUI == &arrangerView);
// only display pop-up if we're using 7SEG or we're not currently in Song / Arranger View
- if (isSessionView && !deluge::hid::display::OLED::isPermanentPopupPresent()) {
+ if (naviview.useNavigationView() && naviview.hasScale) {
+ naviview.drawDashboard();
+ return;
+ }
+ else if (isSessionView && !deluge::hid::display::OLED::isPermanentPopupPresent()) {
sessionView.displayCurrentRootNoteAndScaleName(deluge::hid::display::OLED::main, popupMsg, true);
deluge::hid::display::OLED::markChanged();
return;
diff --git a/src/deluge/playback/playback_handler.cpp b/src/deluge/playback/playback_handler.cpp
index 7ebeecc5d4..5baa709b8e 100644
--- a/src/deluge/playback/playback_handler.cpp
+++ b/src/deluge/playback/playback_handler.cpp
@@ -27,6 +27,7 @@
#include "gui/views/arranger_view.h"
#include "gui/views/automation_view.h"
#include "gui/views/instrument_clip_view.h"
+#include "gui/views/navigation_view.h"
#include "gui/views/performance_view.h"
#include "gui/views/session_view.h"
#include "gui/views/view.h"
@@ -2150,7 +2151,9 @@ void PlaybackHandler::tempoEncoderAction(int8_t offset, bool encoderButtonPresse
else {
if (!isExternalClockActive()) {
UI* currentUI = getCurrentUI();
- bool isOLEDSessionView = display->haveOLED() && (currentUI == &sessionView || currentUI == &arrangerView);
+ bool isOLEDSessionView = display->haveOLED()
+ && (currentUI == &sessionView || currentUI == &arrangerView
+ || (naviview.useNavigationView() && naviview.hasTempoBPM));
if (display->hasPopupOfType(PopupType::TEMPO) || isOLEDSessionView) {
// Truth table for how we decide between adjusting coarse and fine tempo:
//
@@ -2327,8 +2330,11 @@ void PlaybackHandler::displayTempoBPM(float tempoBPM) {
if (display->haveOLED()) {
UI* currentUI = getCurrentUI();
// if we're currently in song or arranger view, we'll render tempo on the display instead of a popup
- if ((currentUI == &sessionView || currentUI == &arrangerView)
- && !deluge::hid::display::OLED::isPermanentPopupPresent()) {
+ if (naviview.useNavigationView() && naviview.hasTempoBPM && !naviview.hasRemainingCountdown) {
+ naviview.drawTempoBPM();
+ }
+ else if ((currentUI == &sessionView || currentUI == &arrangerView)
+ && !deluge::hid::display::OLED::isPermanentPopupPresent() && !naviview.useNavigationView()) {
sessionView.lastDisplayedTempo = tempoBPM;
getTempoStringForOLED(tempoBPM, text);
sessionView.displayTempoBPM(deluge::hid::display::OLED::main, text, true);