From 5c598ab1db1d23b92896d0fccdd3905b8ae04f25 Mon Sep 17 00:00:00 2001 From: "Nicholas J. Michalek" Date: Thu, 9 Feb 2023 09:25:11 -0500 Subject: [PATCH] EuclidX: latest version Added Padding parameter, bjorklund fixes --- software/o_c_REV/HEM_EuclidX.ino | 155 ++++++++++++++++++---------- software/o_c_REV/HemisphereApplet.h | 36 ++++++- software/o_c_REV/bjorklund.cpp | 12 ++- software/o_c_REV/bjorklund.h | 2 +- 4 files changed, 143 insertions(+), 62 deletions(-) diff --git a/software/o_c_REV/HEM_EuclidX.ino b/software/o_c_REV/HEM_EuclidX.ino index 4b113cd01..3419caee8 100644 --- a/software/o_c_REV/HEM_EuclidX.ino +++ b/software/o_c_REV/HEM_EuclidX.ino @@ -30,14 +30,22 @@ #include "bjorklund.h" -const int NUM_PARAMS = 4; +const int NUM_PARAMS = 5; const int PARAM_SIZE = 5; class EuclidX : public HemisphereApplet { public: + enum EuclidXParam { + LENGTH1, BEATS1, OFFSET1, PADDING1, + LENGTH2, BEATS2, OFFSET2, PADDING2, + CV_DEST1, + CV_DEST2, + LAST_SETTING = CV_DEST2 + }; + const char* applet_name() { - return "AnnularFu"; + return "EuclidX"; } void Start() { @@ -46,7 +54,8 @@ public: actual_length[ch] = length[ch] = 16; actual_beats[ch] = beats[ch] = 4 + (ch * 4); actual_offset[ch] = offset[ch] = 0; - pattern[ch] = EuclideanPattern(length[ch], beats[ch], 0); + actual_padding[ch] = padding[ch] = 0; + pattern[ch] = EuclideanPattern(length[ch], beats[ch], offset[ch], padding[ch]); } step = 0; } @@ -63,25 +72,29 @@ public: actual_length[ch] = length[ch]; actual_beats[ch] = beats[ch]; actual_offset[ch] = offset[ch]; + actual_padding[ch] = padding[ch]; // process CV inputs ForEachChannel(cv_ch) { - switch (cv_dest[cv_ch] - ch * (NUM_PARAMS-1)) { - case 0: // length - actual_length[ch] = constrain(actual_length[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_CV, 31), 1, 32); + switch (cv_dest[cv_ch] - ch * LENGTH2) { // this is dumb, but efficient + case LENGTH1: + actual_length[ch] = constrain(actual_length[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_CV, 31), 2, 32); break; - case 1: // beats + case BEATS1: actual_beats[ch] = constrain(actual_beats[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_CV, actual_length[ch]), 1, actual_length[ch]); break; - case 2: // offset - actual_offset[ch] = constrain(actual_offset[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_CV, actual_length[ch]), 0, actual_length[ch]-1); + case OFFSET1: + actual_offset[ch] = constrain(actual_offset[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_CV, actual_length[ch] + actual_padding[ch]), 0, actual_length[ch] + padding[ch] - 1); break; + case PADDING1: + actual_padding[ch] = constrain(actual_padding[ch] + Proportion(cv_data[cv_ch], HEMISPHERE_MAX_CV, 32 - actual_length[ch]), 0, 32 - actual_length[ch]); + break; default: break; } } // Store the pattern for display - pattern[ch] = EuclideanPattern(actual_length[ch], actual_beats[ch], actual_offset[ch]); + pattern[ch] = EuclideanPattern(actual_length[ch], actual_beats[ch], actual_offset[ch], actual_padding[ch]); } // Process triggers and step forward on clock @@ -89,14 +102,14 @@ public: ForEachChannel(ch) { // actually output the triggers - int sb = step % actual_length[ch]; + int sb = step % (actual_length[ch] + actual_padding[ch]); if ((pattern[ch] >> sb) & 0x01) { ClockOut(ch); } } // Plan for the thing to run forever and ever - if (++step >= actual_length[0] * actual_length[1]) step = 0; + if (++step >= (actual_length[0]+actual_padding[0]) * (actual_length[1]+actual_padding[1])) step = 0; } } @@ -107,27 +120,40 @@ public: } void OnButtonPress() { - if (++cursor > 7) cursor = 0; - ResetCursor(); + CursorAction(cursor, LAST_SETTING); } void OnEncoderMove(int direction) { - int ch = cursor < NUM_PARAMS ? 0 : 1; - int f = cursor - (ch * NUM_PARAMS); // Cursor function - switch (f) { - case 0: - actual_length[ch] = length[ch] = constrain(length[ch] + direction, 3, 32); + if (!EditMode()) { + MoveCursor(cursor, direction, LAST_SETTING); + return; + } + + int ch = cursor < LENGTH2 ? 0 : 1; + switch (cursor) { + case LENGTH1: + case LENGTH2: + actual_length[ch] = length[ch] = constrain(length[ch] + direction, 2, 32); if (beats[ch] > length[ch]) beats[ch] = length[ch]; - if (offset[ch] >= length[ch]) offset[ch] = length[ch]-1; + if (padding[ch] > 32 - length[ch]) padding[ch] = 32 - length[ch]; + if (offset[ch] >= length[ch] + padding[ch]) offset[ch] = length[ch] + padding[ch] - 1; break; - case 1: + case BEATS1: + case BEATS2: actual_beats[ch] = beats[ch] = constrain(beats[ch] + direction, 1, length[ch]); break; - case 2: - actual_offset[ch] = offset[ch] = constrain(offset[ch] + direction, 0, length[ch] - 1); + case OFFSET1: + case OFFSET2: + actual_offset[ch] = offset[ch] = constrain(offset[ch] + direction, 0, length[ch] + padding[ch] - 1); + break; + case PADDING1: + case PADDING2: + padding[ch] = constrain(padding[ch] + direction, 0, 32 - length[ch]); + break; + case CV_DEST1: + case CV_DEST2: + cv_dest[cursor - CV_DEST1] = (EuclidXParam) constrain(cv_dest[cursor - CV_DEST1] + direction, LENGTH1, PADDING2); break; - case 3: // CV destination - cv_dest[ch] = constrain(cv_dest[ch] + direction, 0, 5); } } @@ -141,6 +167,8 @@ public: Pack(data, PackLocation {5 * PARAM_SIZE, PARAM_SIZE}, offset[1]); Pack(data, PackLocation {6 * PARAM_SIZE, PARAM_SIZE}, cv_dest[0]); Pack(data, PackLocation {7 * PARAM_SIZE, PARAM_SIZE}, cv_dest[1]); + Pack(data, PackLocation {8 * PARAM_SIZE, PARAM_SIZE}, padding[0]); + Pack(data, PackLocation {9 * PARAM_SIZE, PARAM_SIZE}, padding[1]); return data; } @@ -151,8 +179,10 @@ public: actual_beats[1] = beats[1] = Unpack(data, PackLocation {3 * PARAM_SIZE, PARAM_SIZE}) + 1; actual_offset[0] = offset[0] = Unpack(data, PackLocation {4 * PARAM_SIZE, PARAM_SIZE}); actual_offset[1] = offset[1] = Unpack(data, PackLocation {5 * PARAM_SIZE, PARAM_SIZE}); - cv_dest[0] = Unpack(data, PackLocation {6 * PARAM_SIZE, PARAM_SIZE}); - cv_dest[1] = Unpack(data, PackLocation {7 * PARAM_SIZE, PARAM_SIZE}); + cv_dest[0] = (EuclidXParam) Unpack(data, PackLocation {6 * PARAM_SIZE, PARAM_SIZE}); + cv_dest[1] = (EuclidXParam) Unpack(data, PackLocation {7 * PARAM_SIZE, PARAM_SIZE}); + actual_padding[0] = padding[0] = Unpack(data, PackLocation {8 * PARAM_SIZE, PARAM_SIZE}); + actual_padding[1] = padding[1] = Unpack(data, PackLocation {9 * PARAM_SIZE, PARAM_SIZE}); } protected: @@ -167,35 +197,36 @@ protected: private: int step; - int cursor = 0; // Ch1: 0=Length, 1=Hits; Ch2: 2=Length 3=Hits + int cursor = LENGTH1; // EuclidXParam uint32_t pattern[2]; // Settings uint8_t length[2]; uint8_t beats[2]; uint8_t offset[2]; + uint8_t padding[2]; uint8_t actual_length[2]; uint8_t actual_beats[2]; uint8_t actual_offset[2]; + uint8_t actual_padding[2]; - uint8_t cv_dest[2]; + EuclidXParam cv_dest[2] = {BEATS1, BEATS2}; // input modulation void DrawSteps() { - //int spacing = 1; gfxLine(0, 45, 63, 45); gfxLine(0, 62, 63, 62); gfxLine(0, 53, 63, 53); gfxLine(0, 54, 63, 54); ForEachChannel(ch) { for (int i = 0; i < 16; i++) { - if ((pattern[ch] >> ((i + step) % actual_length[ch])) & 0x1) { + if ((pattern[ch] >> ((i + step) % (actual_length[ch]+actual_padding[ch]) )) & 0x1) { gfxRect(4 * i + 1, 48 + 9 * ch, 3, 3); //gfxLine(4 * i + 2, 47 + 9 * ch, 4 * i + 2, 47 + 9 * ch + 4); } else { gfxPixel(4 * i + 2, 47 + 9 * ch + 2); } - if ((i + step) % actual_length[ch] == 0) { + if ((i + step) % (actual_length[ch]+actual_padding[ch]) == 0) { //gfxLine(4 * i, 46 + 9 * ch, 4 * i, 52 + 9 * ch); gfxLine(4 * i, 46 + 9 * ch, 4 * i, 46 + 9 * ch + 1); gfxLine(4 * i, 52 + 9 * ch - 1, 4 * i, 52 + 9 * ch); @@ -205,38 +236,54 @@ private: } void DrawEditor() { - int spacing = 18; + const int spacing = 16; - gfxBitmap(4 + 0 * spacing, 15, 8, LOOP_ICON); - gfxBitmap(4 + 1 * spacing, 15, 8, X_NOTE_ICON); - gfxBitmap(4 + 2 * spacing, 15, 8, LEFT_RIGHT_ICON); + if (cursor < CV_DEST1) { + gfxBitmap(8 + 0 * spacing, 15, 8, LOOP_ICON); + } + gfxBitmap(8 + 1 * spacing, 15, 8, X_NOTE_ICON); + gfxBitmap(8 + 2 * spacing, 15, 8, LEFT_RIGHT_ICON); + gfxPrint(8 + 3 * spacing, 15, "+"); + int y = 15; ForEachChannel (ch) { - int y = 15 + 10 * (ch + 1); - gfxPrint(4 + 0 * spacing, y, actual_length[ch]); - gfxPrint(4 + 1 * spacing, y, actual_beats[ch]); - gfxPrint(4 + 2 * spacing, y, actual_offset[ch]); - - int f = cursor - ch * NUM_PARAMS; - switch (f) { - case 0: - case 1: - case 2: - gfxCursor(4 + f * spacing, y + 7, 12); - break; - case 3: // CV dest selection - gfxBitmap(1 + 3 * spacing, y, 8, CV_ICON); - break; - } + y += 10; + gfxPrint(3 + 0 * spacing + pad(10, actual_length[ch]), y, actual_length[ch]); + gfxPrint(3 + 1 * spacing + pad(10, actual_beats[ch]), y, actual_beats[ch]); + gfxPrint(3 + 2 * spacing + pad(10, actual_offset[ch]), y, actual_offset[ch]); + gfxPrint(3 + 3 * spacing + pad(10, actual_padding[ch]), y, actual_padding[ch]); // CV assignment indicators ForEachChannel(ch_dest) { - int ff = cv_dest[ch_dest] - (NUM_PARAMS-1)*ch; - if (ff >= 0 && ff < (NUM_PARAMS-1)) + int ff = cv_dest[ch_dest] - LENGTH2*ch; + if (ff >= 0 && ff < LENGTH2) gfxBitmap(ff * spacing, y, 3, ch_dest?SUB_TWO:SUP_ONE); } } + int ch = cursor < LENGTH2 ? 0 : 1; + int f = cursor - ch * LENGTH2; + y = 33; + switch (cursor) { + case LENGTH2: + case BEATS2: + case OFFSET2: + case PADDING2: + y += 10; + case LENGTH1: + case BEATS1: + case OFFSET1: + case PADDING1: + gfxCursor(3 + f * spacing, y, 13); + break; + + case CV_DEST1: + case CV_DEST2: + gfxBitmap(0, 13 + (cursor - CV_DEST1)*5, 8, CV_ICON); + gfxBitmap(8, 15, 3, (cursor - CV_DEST1)? SUB_TWO : SUP_ONE); + gfxCursor(0, 19 + (cursor - CV_DEST1)*5, 11, 7); + break; + } } }; diff --git a/software/o_c_REV/HemisphereApplet.h b/software/o_c_REV/HemisphereApplet.h index a8162adda..41d15118f 100644 --- a/software/o_c_REV/HemisphereApplet.h +++ b/software/o_c_REV/HemisphereApplet.h @@ -74,6 +74,10 @@ typedef struct PackLocation { class HemisphereApplet { public: + static uint8_t modal_edit_mode; + static void CycleEditMode() { + ++modal_edit_mode %= 3; + } virtual const char* applet_name(); // Maximum of 9 characters virtual void Start(); @@ -181,10 +185,35 @@ class HemisphereApplet { } } + // handle modal edit mode toggle or cursor advance + void CursorAction(int &cursor, int max) { + if (modal_edit_mode) { + isEditing = !isEditing; + } else { + cursor++; + cursor %= max + 1; + ResetCursor(); + } + } + void MoveCursor(int &cursor, int direction, int max) { + cursor += direction; + if (modal_edit_mode == 2) { // wrap cursor + if (cursor < 0) cursor = max; + else cursor %= max + 1; + } else { + cursor = constrain(cursor, 0, max); + } + ResetCursor(); + } + bool EditMode() { + return (isEditing || !modal_edit_mode); + } + //////////////// Offset graphics methods //////////////////////////////////////////////////////////////////////////////// - void gfxCursor(int x, int y, int w) { - if (CursorBlink()) gfxLine(x, y, x + w - 1, y); + void gfxCursor(int x, int y, int w, int h = 9) { // assumes standard text height for highlighting + if (isEditing) gfxInvert(x, y - h, w, h); + else if (CursorBlink()) gfxLine(x, y, x + w - 1, y); } void gfxPos(int x, int y) { @@ -376,6 +405,7 @@ class HemisphereApplet { protected: bool hemisphere; // Which hemisphere (0, 1) this applet uses + bool isEditing = false; // modal editing toggle const char* help[4]; virtual void SetHelp(); @@ -470,3 +500,5 @@ class HemisphereApplet { bool changed_cv[2]; // Has the input changed by more than 1/8 semitone since the last read? int last_cv[2]; // For change detection }; + +uint8_t HemisphereApplet::modal_edit_mode = 0; // 0=old behavior, 1=modal editing, 2=modal with wraparound diff --git a/software/o_c_REV/bjorklund.cpp b/software/o_c_REV/bjorklund.cpp index a476d38e2..01ad12616 100644 --- a/software/o_c_REV/bjorklund.cpp +++ b/software/o_c_REV/bjorklund.cpp @@ -1334,12 +1334,14 @@ bool EuclideanFilter(uint8_t num_steps, uint8_t num_beats, uint8_t rotation, uin return static_cast(pattern & (0x01 << clock)) ; } -uint32_t EuclideanPattern(uint8_t num_steps, uint8_t num_beats, uint8_t rotation) { - num_beats %= (num_steps + 1); - uint32_t pattern = bjorklund_patterns[((num_steps - 2) * 33) + num_beats]; +uint32_t EuclideanPattern(uint8_t num_steps, uint8_t num_beats, uint8_t rotation, uint8_t padding) { + if (num_steps < 2) num_steps = 2; + if (num_beats > num_steps) num_beats = num_steps; + + uint32_t pattern = bjorklund_patterns[((num_steps - 2) * 33) + num_beats]; if (rotation) { - rotation %= num_steps; - pattern = rotl32(pattern, num_steps, rotation) ; + rotation = rotation % (num_steps + padding); + pattern = rotl32(pattern, num_steps + padding, rotation) ; } return pattern; } diff --git a/software/o_c_REV/bjorklund.h b/software/o_c_REV/bjorklund.h index cabb3fece..91d69a7fb 100644 --- a/software/o_c_REV/bjorklund.h +++ b/software/o_c_REV/bjorklund.h @@ -49,6 +49,6 @@ inline uint32_t rotl32(uint32_t input, unsigned int length, unsigned int count) } bool EuclideanFilter(uint8_t num_steps, uint8_t num_beats, uint8_t rotation, uint32_t clock); -uint32_t EuclideanPattern(uint8_t num_steps, uint8_t num_beats, uint8_t rotation); +uint32_t EuclideanPattern(uint8_t num_steps, uint8_t num_beats, uint8_t rotation, uint8_t padding = 0); #endif // BJORKLUND_H_