Why is each y position pos * 9 + 1

Hi, my sketch has menu items in a list, one below the next, but each items "y" position is "pos * 9 + 1"
how does that work?
shouldn't they each be 0-240 (size of display)

fvduino.ino (37.4 KB)

Please follow the advice given in the link below when posting code , use code tags and post the code here to make it easier to read and copy for examination

It looks like your controls are the full width of the screen, but only a few pixels (9?) high.

#include <Arduino.h>
//#include <Bounce2.h>
#include <EEPROM.h>
#include <ButtonEvents.h>
#include  <avr/power.h>

typedef unsigned char PROGMEM prog_uchar;

#include "HW_I2CMaster.h"
#include "font.h"

const uint16_t MARKED = 0x3000; //0x0020
const uint16_t NONE   = 0xFFFF; // this is used for rectangle inner color when not used

#define BypassFSW A0

////////////////////////////////////////////////////////////////////////////////////////////////
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Arduino_ST7789_Fast.h>
#define TFT_DC  A2
#define TFT_RST 1
#define SCR_WD   240
#define SCR_HT   240
Arduino_ST7789 tft = Arduino_ST7789(TFT_DC, TFT_RST);
////////////////////////////////////////////////////////////////////////////////////////////////


// ----------------------------------------NEOPIXEL---------------------------------------------
#include <Adafruit_NeoPixel.h>
#define PIXEL_PIN     7
#define PIXEL_COUNT   1

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, NEO_GRB + NEO_KHZ800);


// Yellow Preset 1
int r0 = 255;
int g0 = 255;
int b0 = 0;

// Green Preset 2
int r1 = 0;
int g1 = 255;
int b1 = 0;

// Red Preset 3
int r2 = 255;
int g2 = 0;
int b2 = 0;

// Grey Preset 4
int r3 = 128;
int g3 = 128;
int b3 = 128;

// Blue Preset 5
int r4 = 0;
int g4 = 0;
int b4 = 255;

// Magenta Preset 6
int r5 = 255;
int g5 = 0;
int b5 = 255;

// Orange Preset 7
int r6 = 230;
int g6 = 80;
int b6 = 0;

// Cyan Preset 8
int r7 = 0;
int g7 = 255;
int b7 = 255;

// ----------------------------------------NEOPIXEL---------------------------------------------


// Pin configuration

// Digital potentiometer uses hardware I2C (TWI), pins A4 and A5
const uint8_t POT_ENABLE_PIN = A3;
//const uint8_t POT_SDA_PIN = A4;
//const uint8_t POT_SCL_PIN = A5;

// Three PWM outputs
const uint8_t PAR1_PWM_PIN = 3;
const uint8_t PAR2_PWM_PIN = 9;
const uint8_t PAR3_PWM_PIN = 10;

// Two buttons.
const uint8_t PresetFSW = 8; // Preset Footswitch
const uint8_t buttonPin = 0;   // Select/Save Encoder Button

//ButtonEvents myButton; // create an instance of the ButtonEvents class to attach to our button

// Encoder
const uint8_t ENC1_PIN = 2;
const uint8_t ENC2_PIN = A1;

// EEPROM emulation and FV-1 program switching
const uint8_t PC_NOTIFY_PIN = 4;
const uint8_t I2C_EEPROM_EMUL_SDA_PIN = 5;
const uint8_t I2C_EEPROM_EMUL_SCL_PIN = 6;
const uint8_t EEPROM_EMUL_ADDR = 0x50;

// OLED display commununication
// For reference here, oled code uses port and bit banging
//const uint8_t OLED_DATA_PIN = 13;
//const uint8_t OLED_CS_PIN   = 12;
//const uint8_t OLED_CLK_PIN  = 11;

// Digipot address
const uint8_t DS1881_BASE_I2C_ADDR = 0x28;
const uint8_t DS1881_write_address = DS1881_BASE_I2C_ADDR << 1;
const uint8_t DS1881_read_address = (DS1881_BASE_I2C_ADDR << 1) | _BV(0);


/*********** FV-1 algorithm data structures ***********/

// The eeprom data is in 16 * 32 byte banks (total 512 bytes)
// They are compressed by LZ algorithm (compress.py) to approx.
// half the size. Include the algorithm data blocks here.

#include "spinsemi.h"



// Strings for describing the algorithm and parameters
typedef struct {
  char algoname [14];
  char par1name [6];
  char par2name [6];
  char par3name [6];
} Description;

// Sensible default settings for each algorithm
typedef struct {
  uint8_t vol;
  uint8_t mix;
  uint8_t par1;
  uint8_t par2;
  uint8_t par3;
} Default;

// Keep everything together in a single array
typedef struct {
  Description desc;
  Default def;
  uint8_t* const prog_addr;
} AlgoDatum;

const AlgoDatum algodata [] PROGMEM = {

  {{"Hall",          "Pre",   "Time",  "Damp" }, {15, 10, 128, 128, 128}, (uint8_t *)K3_V1_0_Hall},
  {{"Room",          "Pre",   "Time",  "Damp" }, {15, 10, 128, 128, 128}, (uint8_t *)K3_V1_1_Room},
  {{"Plate Reverb",  "PreD",  "Time",  "Damp" }, {15, 10, 128, 128, 128}, (uint8_t *)K3_V1_2_PLATE},
  {{"Rep Echo/Rev",  "Delay", "Rep",   "Revrb"}, {15, 10, 128, 128, 128}, (uint8_t *)K3_V1_4_ECHO_REV},
  {{"Chorus+Revrb",  "Width", "Sweep", "Revrb"}, {15, 20, 128, 128, 128}, (uint8_t *)K3_V1_6_CHOR_REV},
  {{"Echo/Revrb2",   "Revrb", "Time",  "Level"}, {15, 10, 128, 128, 128}, (uint8_t *)GA_DEMO_ECHO},
  {{"Flanger+Rev",   "Revrb", "Rate",  "Depth"}, {15, 20, 128, 128, 128}, (uint8_t *)GA_DEMO_FLANGE},
  {{"Phaser+Rev",    "Revrb", "Rate",  "Width"}, {15, 20, 128, 128, 128}, (uint8_t *)GA_DEMO_PHASE},
  {{"VibraVerb",       "Revrb", "Speed", "Depth"}, {15, 20, 128, 128, 128}, (uint8_t *)GA_DEMO_VIBRATO},
  {{"Filter Verb",   "Revrb", "Sens", "Depth"},  {15, 20, 128, 128, 128}, (uint8_t *)GA_DEMO_WAH},

  {{"Rom Rev 1",     "Par 1", "Par 2", "Par 3"}, {15, 10, 128, 128, 128}, (uint8_t *)rom_rev1},
  {{"Rom Rev 2",     "Par 1", "Par 2", "Par 3"}, {15, 10, 128, 128, 128}, (uint8_t *)rom_rev2},
  {{"Tremolo+Rev",   "Revrb", "Rate",  "Trem" }, {15, 10, 128, 128, 128}, (uint8_t *)rom_trem_rev},
  {{"Spring+Trem",   "Revrb", "Rate", "Depth"},  {15, 10, 128, 128, 128}, (uint8_t *)spring_verb},
  {{"Reverse Dly",   "Par 1", "Par 2", "Par 3"}, {15, 10, 128, 128, 128}, (uint8_t *)afx},
  {{"Abbey Reverb",  "Par 1", "Par 2", "Par 3"}, {15, 20, 128, 128, 128}, (uint8_t *)abbeyverb},
  {{"Choirsaw",      "Delay", "Width", "Trem"},  {15, 10, 128, 128, 128}, (uint8_t *)choirsaw},
  {{"Blue Nebula",   "Par 1", "Par 2", "Par 3"}, {15, 10, 128, 128, 128}, (uint8_t *)bluenebula},
  {{"Shimmer",       "Dwell", "Pitch", "Mix"},   {15, 10, 128, 128, 128}, (uint8_t *)glimmer},
  {{"Flanger",       "Par 1", "Par 2", "Par 3"}, {15, 10, 128, 128, 128}, (uint8_t *)flanger},


};

// The space for printing algo number on screen is two characters
// In practice, about 80 algorithms is the max that fits on atmega328 32k memory
const uint8_t NALGO = min(99, sizeof(algodata) / sizeof(AlgoDatum));
// Algorithm will be copied to RAM before sending because PROGMEM is too slow
uint8_t algo_buffer[512];

// The strings of the currently visible algorithm are copied to ram
Description current_algo_strings;

/************** Patch data structures *****************/

typedef struct {
  uint8_t algorithm; // index
  uint8_t vol;
  uint8_t mix;
  uint8_t par1;
  uint8_t par2;
  uint8_t par3;
} Patch;

// 5 RGB 565 colours for each patch, from dark to light
const uint16_t patch_colors [][5] = {
  {0x5940, 0xD1A0, 0xFC20, 0xFD80, 0xFE85},   // Amber
  {0x0220, 0x0C80, 0x3640, 0x4F29, 0xA7D4},   // Green
  {0x7820, 0xE060, 0xFA8A, 0xFBCF, 0xFDD7},   // Red
  {0x31E8, 0x4ACC, 0x638F, 0xA577, 0xC639},   // Gray
  {0x00C9, 0x0230, 0x0457, 0x965B, 0xDF5E},   // Blue
  {0x3807, 0x8811, 0xF81F, 0xFC5F, 0xFEDF},   // Mag
  {0x8285, 0x9307, 0xBBE9, 0xD52D, 0xFFFF},   // Brown
  {0x1112, 0x73FD, 0xBD5F, 0x36BC, 0xAFDF},   // Cyan
};

const uint8_t NPATCH = sizeof(patch_colors) / sizeof(patch_colors[0]);

Patch patch_data[NPATCH]; // Keep all patch data in memory, store and restore in EEPROM

const uint16_t P_IN_USE_EE_ADDR = 0;
const uint16_t PATCH_EE_ADDR = 1;
const uint16_t ALGO_EE_ADDR = 512;

uint8_t changed[NPATCH];        // to keep track of patch edit

// State machine for user interface
enum EditState {None, Select, Change};

EditState es = None;
uint8_t selection = 5;            // default start from line 5
uint8_t patches_in_use = 2;
uint8_t current_patch = 0;
uint16_t bg_col = BLACK;          // bg_col will be different if patch is edited but not saved


/************** Digipot code ****************/


/* Calculate actual pot positions from vol and mix values

   vol goes from 0 to 20
   17 is unity gain (corresponding to pot volume 60)

   mix goes from 0 to 20
   10 is 1:1 mix max wet - max dry
   below 10 is less wet - max dry
   above 10 is max wet  - less dry
*/
uint8_t dry_from(uint8_t vol, uint8_t mix)
{
  if (mix > 20)
    mix = 20;

  if (vol > 20)
    vol = 20;

  if (vol == 0 || mix == 20)
    return 0;  // dry is muted

  uint8_t nominal_vol = 43 + vol; // Max 63

  if (mix <= 10)
    return nominal_vol; // dry is maxed

  uint8_t vol_adjustment = mix - 10;
  if (vol_adjustment > nominal_vol)
    return 0;           // dry is muted

  return nominal_vol - vol_adjustment;
}

uint8_t wet_from(uint8_t vol, uint8_t mix)
{

  if (mix > 20)
    mix = 20;

  if (vol > 20)
    vol = 20;

  if (vol == 0 || mix == 0)
    return 0;  // wet is muted

  uint8_t nominal_vol = 43 + vol; // Max 63

  if (mix >= 10)
    return nominal_vol; // wet is maxed

  uint8_t vol_adjustment = 10 - mix;
  if (vol_adjustment > nominal_vol)
    return 0;           // wet is muted

  return nominal_vol - vol_adjustment;
}


/* Run only once during factory default setup.

*/
bool pot_init()
{
  digitalWrite(POT_ENABLE_PIN, LOW); // enable pot communication

  if (!i2c_start(DS1881_write_address))
    return false; // no reply from device

  i2c_write(0b10000010);       // Nonvolatile writes: Zero crossing, 63 values + mute
  i2c_stop();
  //delay(20); // wait for EEPROM write to finish
  delay(10); // half delay when clocking nano to 8 MHz

  i2c_start(DS1881_write_address);
  i2c_write(0b10000110);       // Go back to volatile writes
  i2c_stop();
  //delay(20); // wait for EEPROM write to finish
  delay(10); // half delay when clocking nano to 8 MHz

  digitalWrite(POT_ENABLE_PIN, HIGH);

  return true;
}

/* Set the digipot to given value.
   Min volume 0: -80db attenuation,
   Max volume 63:  0db attenuation
*/
bool pot_set(uint8_t pot, uint8_t val)
{
  val &= 0b00111111; // only the low 6 bits matter

  val = 63 - val;    // invert the value.
  // pot acts as attenuator: 0 - no attenuation (max volume)
  // we want larger number to mean louder

  if (pot)
    val |= 0b01000000; // set the pot bit;

  digitalWrite(POT_ENABLE_PIN, LOW); // enable pot communication

  if (!i2c_start(DS1881_write_address))
    return false;  // no reply from device

  bool res = i2c_write(val);
  i2c_stop();

  digitalWrite(POT_ENABLE_PIN, HIGH);

  return res;
}


/*************** User Interface code **********************/
using Bounce2::Button;

Button footswitch = Button();

ButtonEvents myButton; // create an instance of the ButtonEvents class to attach to our button

void draw_selection(const uint16_t* colors, uint8_t pos, bool emph)
{
  uint8_t y = pos * 9;
  uint16_t col = emph ? colors[4] : colors[2];

  draw_rect(0 , y, SCR_WD , 10, col, NONE, false); // Selection box
}


void clear_selection(uint8_t pos)
{
  uint8_t y = pos * 9;

  draw_rect(0 , y, SCR_WD , 10, bg_col, NONE, false);
}


void draw_patch ()
{
  tft.fillRect(0, 54, SCR_WD, 10, bg_col); // clear algorithm name area with background color
  char buff[3];              // 3 chars + \0
  buff[0] =  ' ';            // put spaces in tens and hundreds position

  byte algo = patch_data[current_patch].algorithm + 1;
  btoa(algo, buff + 3); // give pointer to just after buffer (end)

  // Print algo number
  //plot_text(83, 6 * 9 + 1, buff, patch_colors[current_patch][4]);
  tft.setCursor(220, 6 * 9 + 1);
  tft.setTextColor(patch_colors[current_patch][4]);
  tft.setTextSize(1);
  tft.print(buff); // Algo number

  // Print algo name
  //  plot_text(2, 6 * 9 + 1, current_algo_strings.algoname, patch_colors[current_patch][4]);
  tft.setCursor(2, 6 * 9 + 1);
  tft.setTextColor(patch_colors[current_patch][4]);
  tft.setTextSize(1);
  tft.print(current_algo_strings.algoname); // Algo name

  const uint16_t * palette = patch_colors[current_patch];

  clear_selection(5);
  draw_volume(palette, bg_col, 5, patch_data[current_patch].vol);
  clear_selection(4);
  draw_mix(palette, bg_col, 4, patch_data[current_patch].mix);
  clear_selection(3);
  draw_parameter(palette, bg_col, current_algo_strings.par1name, 3, patch_data[current_patch].par1);
  clear_selection(2);
  draw_parameter(palette, bg_col, current_algo_strings.par2name, 2, patch_data[current_patch].par2);
  clear_selection(1);
  draw_parameter(palette, bg_col, current_algo_strings.par3name, 1, patch_data[current_patch].par3);
}

void draw_panel()
{
  uint8_t boxlen = SCR_WD / NPATCH; // Preset boxes

  tft.drawRect(1, 1, 238, 8, bg_col);
  tft.fillRect(1, 1, 238, 8, bg_col);

  for (uint8_t i = 0; i < patches_in_use; ++i) {
    if (i == current_patch) {
      tft.drawRect(boxlen * i , 1, boxlen , 8, patch_colors[i][2]);
      tft.fillRect(boxlen * i , 1, boxlen , 8, patch_colors[i][3]);
    } else {
      tft.drawRect(boxlen * i , 1, boxlen , 8, bg_col);
      tft.fillRect(boxlen * i , 1, boxlen , 8, patch_colors[i][0]);
    }
  }
}


void change_selection(const uint16_t * palette, int8_t dir)
{
  if (dir < -1) dir = -1;
  if (dir > 1) dir = 1;

  uint8_t oldsel = selection;
  selection += dir + 7;
  selection %= 7;
  clear_selection(oldsel);
  draw_selection(palette, selection, false);
}

void set_patch()
{
  // Turn digipots
  uint8_t dry = dry_from(patch_data[current_patch].vol, patch_data[current_patch].mix);
  uint8_t wet = wet_from(patch_data[current_patch].vol, patch_data[current_patch].mix);

  pot_set(0, dry);
  pot_set(1, wet);

  // Set analog signal to FV-1
  analogWrite(PAR1_PWM_PIN, patch_data[current_patch].par1);
  analogWrite(PAR2_PWM_PIN, patch_data[current_patch].par2);
  analogWrite(PAR3_PWM_PIN, patch_data[current_patch].par3);
}

void change_patch(int8_t dir)
{
  current_patch += dir + patches_in_use;
  current_patch %= patches_in_use;

  bg_col = changed[current_patch] ? MARKED : BLACK;

  // Save active patch to EEPROM?
  // 100000 writes promised -> 1000 days 100 patch changes per day
  // Maybe not worth it...

  send_algo();
  set_patch();

  // copy current patch strings for ui
  uint8_t algo = patch_data[current_patch].algorithm;
  memcpy_P(&current_algo_strings, &(algodata[algo].desc), sizeof current_algo_strings);

  // print changed gui
  draw_panel();
  draw_patch();

}

// Valid values for dir are -1 and 1
void edit_algo(int8_t dir)
{

  patch_data[current_patch].algorithm += dir + NALGO;
  patch_data[current_patch].algorithm %= NALGO;

  uint8_t algo = patch_data[current_patch].algorithm;

  // Copy current patch strings for ui
  memcpy_P(&current_algo_strings, &(algodata[algo].desc), sizeof current_algo_strings);

  // Set params to default values. Copy 5 bytes from EEPROM
  Default tmp;
  EEPROM.get(ALGO_EE_ADDR + algo * sizeof(Default),  tmp);

  patch_data[current_patch].vol  = tmp.vol;
  patch_data[current_patch].mix  = tmp.mix;
  patch_data[current_patch].par1 = tmp.par1;
  patch_data[current_patch].par2 = tmp.par2;
  patch_data[current_patch].par3 = tmp.par3;

  changed[current_patch] = true;

  send_algo();
  set_patch();

  // print updated elements
  draw_patch();

  const uint16_t * palette = patch_colors[current_patch];
  draw_selection(palette, selection, true);
}


void edit_vol(int8_t dir, const uint8_t pos)
{
  uint8_t vol = patch_data[current_patch].vol;

  if (vol == 0 && dir < 0)
    return; // At minimum
  if (vol == 20 && dir > 0)
    return; // Maxed out

  vol += dir;

  // Change the patch
  patch_data[current_patch].vol = vol;
  changed[current_patch] = true;

  // Turn the digipots.
  uint8_t dry = dry_from(vol, patch_data[current_patch].mix);
  uint8_t wet = wet_from(vol, patch_data[current_patch].mix);
  pot_set(0, dry);
  pot_set(1, wet);

  // Draw the change
  const uint16_t * palette = patch_colors[current_patch];
  draw_volume(palette, bg_col, pos, vol);
}

void edit_mix(int8_t dir, const uint8_t pos)
{
  uint8_t mix = patch_data[current_patch].mix;

  if (mix ==  0 && dir < 0)
    return;  // At minimum
  if (mix == 20 && dir > 0)
    return;  // At maximum

  mix += dir;

  // Change the patch
  patch_data[current_patch].mix = mix;
  changed[current_patch] = true;

  // Turn the digipots.
  uint8_t dry = dry_from(patch_data[current_patch].vol, mix);
  uint8_t wet = wet_from(patch_data[current_patch].vol, mix);
  pot_set(0, dry);
  pot_set(1, wet);

  // Draw the change
  const uint16_t * palette = patch_colors[current_patch];
  draw_mix(palette, bg_col, pos, mix);
}

// Valid values for dir are -1 and 1
//void edit_par(int8_t dir, bool fast, uint8_t pin, uint8_t &val, const char *parname, const uint8_t pos)
void edit_par(int8_t dir, uint8_t pin, uint8_t &val, const char *parname, const uint8_t pos)
{
  // if (fast)
  dir *= 10;

  if (dir < 0 && val < -dir)
    val = 0;
  else if (dir > 0 && (255 - val) < dir)
    val = 255;
  else
    val += dir;

  const uint16_t * palette = patch_colors[current_patch];

  changed[current_patch] = true;

  analogWrite(pin, val);
  draw_parameter(palette, bg_col, parname, pos, val);
}

void edit_menubar(int8_t dir)
{
  if (dir < -1) dir = -1;
  if (dir > 1) dir = 1;

  // patches_in_use must be in [2, NPATCH]
  if (patches_in_use <= 2 && dir < 0)
    return;
  if (patches_in_use >= NPATCH && dir > 0)
    return;

  const uint16_t * palette = patch_colors[current_patch];

  patches_in_use += dir;

  if (current_patch >= patches_in_use) {
    change_patch(-1);  // move to previous patch since current is not in use
    draw_selection(palette, selection, true);
  } else {
    draw_panel();
    draw_selection(palette, selection, true);
  }

  // write number of patches in use to eeprom
  EEPROM.put(P_IN_USE_EE_ADDR, patches_in_use);
}

//void edit_patch(uint8_t sel, int8_t dir, bool fast)
void edit_patch(uint8_t sel, int8_t dir)
{
  switch (sel) {
    case 6 : edit_algo(dir);     break;
    case 5 : edit_vol(dir, sel); break;
    case 4 : edit_mix(dir, sel); break;
    case 3 : edit_par(dir, PAR1_PWM_PIN, patch_data[current_patch].par1, current_algo_strings.par1name, sel); break;
    case 2 : edit_par(dir, PAR2_PWM_PIN, patch_data[current_patch].par2, current_algo_strings.par2name, sel); break;
    case 1 : edit_par(dir, PAR3_PWM_PIN, patch_data[current_patch].par3, current_algo_strings.par3name, sel); break;
    case 0 : edit_menubar(dir); break;
  }
}

void save_current_patch()
{
  uint16_t eeAddress = PATCH_EE_ADDR + current_patch * sizeof(Patch);
  Patch curr = patch_data[current_patch];

  EEPROM.put(eeAddress, curr);

  // Save to algo defaults also so that next time they can be used

  eeAddress = ALGO_EE_ADDR + curr.algorithm * sizeof(Default);
  Default tmp = {curr.vol, curr.mix, curr.par1, curr.par2, curr.par3};

  EEPROM.put(eeAddress, tmp);

  changed[current_patch] = false;
  bg_col = BLACK;
  draw_panel();
  draw_patch();             // Redraw the whole screen
}



/********** 24LC32A EEPROM emulation */

/* Compression algorithm based on
   http://excamera.com/sphinx/article-compression.html

   With small modifications.
*/
class Flashbits {
  public:

    void begin(prog_uchar *s)
    {
      src = s;
      mask = curr_byte = 0x00;
    }

    uint8_t get1(void)
    {
      if (!mask) {
        mask = 0x80;
        curr_byte = pgm_read_byte_near(src++);
      }

      uint8_t r = (curr_byte & mask) != 0;
      mask >>= 1;

      return r;
    }

    uint16_t getn(uint8_t n)
    {
      uint16_t r = 0;
      while (n--) {
        r <<= 1;
        r |= get1();
      }
      return r;
    }

  private:
    prog_uchar *src;
    uint8_t mask;
    uint8_t curr_byte;
};

Flashbits bits;

void decompress(uint8_t *dst, prog_uchar *src)
{
  bits.begin(src);
  uint8_t O = bits.getn(4);
  uint8_t L = bits.getn(4);
  uint8_t M = bits.getn(2);

  uint8_t *end = dst + bits.getn(16);

  while (dst != end) {
    if (bits.get1() == 0) {
      *dst++ = bits.getn(8);
    } else {
      int offset = -bits.getn(O) - 1;
      int length = bits.getn(L) + M;
      while (length--) {
        *dst = *(dst + offset);
        dst++;
      }
    }
  }
}

void send_algo()
{
  // Copy FV-1 algorithm from PROGMEM to ram buffer.
  // Progmem is too slow in the send loop.
  // Since we will anyway have to copy from PROGMEM,
  // it makes sense to compress the algorithms and get
  // space savings (almost 2x).

  uint8_t algo = patch_data[current_patch].algorithm;
  decompress(algo_buffer, (prog_uchar *)pgm_read_word(&(algodata[algo].prog_addr)));

  // Set up everything to be ready
  const uint8_t sda_mask = (1 << PIND5);
  const uint8_t clk_mask = (1 << PIND6);
  uint8_t prev_clk = clk_mask;

  DDRD &= ~(clk_mask);   // clk pin as input
  DDRD &= ~(sda_mask);   // sda pin as input
  PORTD &= ~(sda_mask);  // SDA pin is pulled low or floated up
  // -> we need to set pin low only once.
  //    Toggling is done by setting pin to
  //    input (let float high) or output (pull low).
  //    FV-1 internal pullups are ok (tested).

  uint16_t pos = 0;
  uint8_t curr_byte = algo_buffer[pos];
  uint8_t bit_mask = 0b10000000;
  uint8_t clk_count = 0;

  // Undivided attention for FV-1 requests
  noInterrupts();

  // Notify FV-1 of patch change by toggling the notify pin
  PIND = _BV(4);   // Toggle pin 4

  while (clk_count < 37)             // Handle the header
  {
    uint8_t clk = PIND & clk_mask;

    if (!clk && prev_clk) {          // scl went down

      switch (clk_count)
      {
        case 8:
        case 17:
        case 26:
        case 36:
          DDRD  |= sda_mask;         // send ACK - pull sda pin low
          break;
        default:
          DDRD &= ~(sda_mask);       // Release
          break;
      }
      clk_count++;
    }
    prev_clk = clk;
  }

  clk_count = 0;
  while (pos < 512)                  // Send the data
  {
    uint8_t clk = PIND & clk_mask;

    if (!clk && prev_clk) {          // scl went down

      if (clk_count != 8) {          // Sending byte

        if (curr_byte & bit_mask) {
          DDRD &= ~(sda_mask);       // Send 1 = Let High
        } else {
          DDRD  |= sda_mask;         // Send 0 = Pull Low
        }
        bit_mask >>= 1;
        clk_count++;

      } else {                       // Let reciever ACK

        DDRD &= ~(sda_mask);         // Release
        clk_count = 0;
        bit_mask = 0b10000000;
        pos++;
        curr_byte = algo_buffer[pos];
      }
    }
    prev_clk = clk;
  }

  interrupts();

}


void setup()
{
  // ----------------------------------------NEOPIXEL---------------------------------------------
  pixels.begin();
  pixels.setPixelColor(0, 255, 255, 0);
  pixels.show();
  // ----------------------------------------NEOPIXEL---------------------------------------------

  // set up pins

  // Use high frequency PWM
  TCCR1A = (TCCR1A & 0b11111100) | 0b00000001;    // set timer 1 to 8 bit mode
  TCCR1B = (TCCR1B & 0b11100000) | 0b00001001;    // set timer 1 to fast PWM, divisor to 1 for PWM frequency of 31372.55 Hz

  TCCR2A = (TCCR2A & 0b11111100) | 0b00000011;    // set timer 2 to fast PWM
  TCCR2B = (TCCR2B & 0b11111000) | 0b00000001;    // set timer 2 divisor to 1 for PWM frequency of 31372.55 Hz

  // PWM outputs
  pinMode(PAR1_PWM_PIN, OUTPUT);
  pinMode(PAR2_PWM_PIN, OUTPUT);
  pinMode(PAR3_PWM_PIN, OUTPUT);

  // 24LC32 EEPROM emulation
  pinMode(PC_NOTIFY_PIN, OUTPUT);
  pinMode(I2C_EEPROM_EMUL_SDA_PIN, INPUT);
  pinMode(I2C_EEPROM_EMUL_SCL_PIN, INPUT);



  // Buttons and encoder

  pinMode(buttonPin, INPUT_PULLUP);
  myButton.attach(buttonPin);
  myButton.activeHigh();
  myButton.debounceTime(15); //apply 5ms debounce
  myButton.holdTime(750); // require button to be held for 1000ms before triggering a hold event
  footswitch.attach(PresetFSW, INPUT);
  footswitch.interval(5);

  pinMode(ENC1_PIN, INPUT_PULLUP);
  pinMode(ENC2_PIN, INPUT_PULLUP);

  pinMode(BypassFSW, INPUT_PULLUP);

  // SoftI2C is used for digipot control
  pinMode(POT_ENABLE_PIN, OUTPUT);
  i2c_init(); // Uses hardware I2C (pins A4 and A5)


  // TFT Display setup
  tft.init();
  tft.setRotation(1);
  tft.fillScreen(BLACK); // Black
 

  // Write factory defaults to EEPROM if requested
  // Footswitch pressed at boot for 2 sec

  unsigned long startTime = millis();
  unsigned long heldTime = 0;

  while (digitalRead(PresetFSW) == HIGH)

  {
    heldTime = millis() - startTime;
    if (heldTime > 1000)
      break;
  }

  if (heldTime > 1000) {

    // Digipot initial settings, saved in its EEPROM.
    // Depends on i2c_init(). Hangs setup if no pot present...
    pot_init();

    // Write algorithm default settings
    Default tmp_d;
    for (uint8_t i = 0; i < NALGO; ++i) {
      memcpy_P(&tmp_d, &(algodata[i].def), sizeof(Default));
      EEPROM.put(ALGO_EE_ADDR + i * sizeof(Default), tmp_d);
      delay(10);

      rainbow(2);
      pixels.show();
      break;
    }

    // Write empty patches using the first algorithm and default settings
    memcpy_P(&tmp_d, &(algodata[0].def), sizeof(Default));
    Patch tmp_p = {0, tmp_d.vol, tmp_d.mix, tmp_d.par1, tmp_d.par2, tmp_d.par3};

    for (uint8_t i = 0; i < NPATCH; ++i) {
      EEPROM.put(PATCH_EE_ADDR + i * sizeof(Patch), tmp_p);
      delay(10);
    }

    // Set 8 patches as default
    EEPROM.put(P_IN_USE_EE_ADDR, (uint8_t)8);



  }

  // load patch settings from EEPROM
  uint16_t eeAdress = PATCH_EE_ADDR;
  for (uint8_t i = 0; i < NPATCH; ++i) {
    EEPROM.get(eeAdress, patch_data[i]);
    if (patch_data[i].algorithm >= NALGO) {
      patch_data[i].algorithm = 0;   // something wrong with algo index, set to zero
      changed[i] = true;             // user will have to edit and save
    }
    if (patch_data[i].vol > 20)
      patch_data[i].vol = 20;
    if (patch_data[i].mix > 100)
      patch_data[i].mix = 100;
    eeAdress += sizeof(Patch);
  }

  current_patch = 0;

  // get patches_in_use from EEPROM
  EEPROM.get(P_IN_USE_EE_ADDR, patches_in_use);
  if (patches_in_use < 2)
    patches_in_use = 2;
  if (patches_in_use >= NPATCH)
    patches_in_use = NPATCH;

  // copy current patch strings for ui
  uint8_t algo = patch_data[current_patch].algorithm;
  memcpy_P(&current_algo_strings, &(algodata[algo].desc), sizeof current_algo_strings);

  // send current patch algorithm to FV-1
  send_algo();
  set_patch();

  // print initial gui
  draw_panel();
  draw_patch();

}


/* Rotary encoder handling code from:
   https://www.best-microcontroller-projects.com/rotary-encoder.html
*/
uint8_t prevNextCode = 0;
uint16_t store = 0;

// A valid CW or CCW move returns 1 or -1, invalid returns 0.
int8_t read_rotary() {
  const int8_t rot_enc_table[] = {0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0};

  prevNextCode <<= 2;
  if (digitalRead(ENC1_PIN)) prevNextCode |= 0x02;
  if (digitalRead(ENC2_PIN)) prevNextCode |= 0x01;
  prevNextCode &= 0x0f;

  // If valid then store as 16 bit data.
  if  (rot_enc_table[prevNextCode] ) {
    store <<= 4;
    store |= prevNextCode;
    if ((store & 0xff) == 0x2b) return -1;
    if ((store & 0xff) == 0x17) return 1;
  }
  return 0;

}


void loop()
{
  if (digitalRead(BypassFSW) == HIGH)
  {
    if (current_patch == 0) {
      pixels.setPixelColor(0, r0, g0, b0); // Yellow
      pixels.show();
    }
    if (current_patch == 1) {
      pixels.setPixelColor(0, r1, g1, b1); // Green
      pixels.show();
    }
    if (current_patch == 2) {
      pixels.setPixelColor(0, r2, g2, b2);  // Red
      pixels.show();
    }
    if (current_patch == 3) {
      pixels.setPixelColor(0, r3, g3, b3); // Grey
      pixels.show();
    }
    if (current_patch == 4) {
      pixels.setPixelColor(0, r4, g4, b4); // Blue
      pixels.show();
    }
    if (current_patch == 5) {
      pixels.setPixelColor(0, r5, g5, b5); // Magenta
      pixels.show();
    }
    if (current_patch == 6) {
      pixels.setPixelColor(0, r6, g6, b6); // Orange
      pixels.show();
    }
    if (current_patch == 7) {
      pixels.setPixelColor(0, r7, g7, b7); // Cyan
      pixels.show();
    }
  }
  if (digitalRead(BypassFSW) == LOW)
  {
    pixels.setPixelColor(0, 0, 0, 0); // Off
    pixels.show();

  }
  const uint16_t * palette = patch_colors[current_patch];
  if (myButton.rose() == true) {
    if (es == None) {
      es = Select;
      draw_selection(palette, selection, false);
    } else if (es == Select) {
      es = Change;
      // emphasize bar that is being edited
      draw_selection(palette, selection, true);
    } else if (es == Change) {
      es = None;
      if (changed[current_patch]) {
        bg_col = MARKED;
        draw_panel();
        draw_patch();             // Redraw the whole screen
      }
      clear_selection(selection);
    }
  }



  int8_t dir = read_rotary();

  if (myButton.update() == true) {
    switch (myButton.event()) {

      // things to do if the button was held (Hold = Save)
      case (hold) : {
          if (changed[current_patch]) {
            save_current_patch();
            es = None;                               // Go to default mode
            clear_selection(selection);

            rainbow(1);
            pixels.show();
          }
          break;
        }
    }
  }

  footswitch.update();

  // Encoder turned
  if (dir) {
    if (es == None) { // first movement activates Select mode
      es = Select;
      draw_selection(palette, selection, false);
    } else if (es == Select) {
      change_selection(palette, dir);  // Move selection
    } else if (es == Change) {
      //  bool fast = (fast_button.read() == HIGH);
      edit_patch(selection, -dir);       // Change parameter
    }
  }

  // Footswitch pressed
  if (footswitch.pressed()) {
    if (es == Select) {
      clear_selection(selection);
      es = None;

    }
    if (es != Change) {  // Patch change disabled during editing
      change_patch(1);

    }
  }
}

void rainbow(int wait) {
  for (long firstPixelHue = 0; firstPixelHue < 1 * 65536; firstPixelHue += 256) {
    for (int i = 0; i < pixels.numPixels(); i++) {
      int pixelHue = firstPixelHue + (i * 65536L / pixels.numPixels());
      pixels.setPixelColor(i, pixels.gamma32(pixels.ColorHSV(pixelHue)));
    }
    pixels.show(); // Update strip with new contents
    delay(wait);  // Pause for a moment
    pixels.setPixelColor(0, 0, 0, 0);
    pixels.show(); // Update strip with new contents
  }
}

// - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / - / -



// Draw a rectangle in foreground colour optionally filled another colour
void draw_rect (uint8_t x, uint8_t y, uint8_t w, uint8_t h,
                uint16_t edgecol, uint16_t fillcol, boolean filled)
{
  if (w == 0 || h == 0)
    return;

  uint8_t e_red   = (edgecol & 0xf800) >> 10;
  uint8_t e_green = (edgecol & 0x07e0) >> 5;
  uint8_t e_blue  = (edgecol & 0x001f) << 1;

  uint8_t f_red   = (fillcol & 0xf800) >> 10;
  uint8_t f_green = (fillcol & 0x07e0) >> 5;
  uint8_t f_blue  = (fillcol & 0x001f) << 1;

  tft.drawRect(x, y, w, h, edgecol);

  if (filled)                             // filled rectangles take time to draw
    tft.fillRect(x, y, w, h, edgecol);
}

// Send a byte to the display
//void Send(uint8_t d)
//{
//  for (uint8_t bit = 0x80; bit; bit >>= 1) {
//    PINB = 1 << clk;                      // clk low
//    if (d & bit) PORTB = PORTB | (1 << data); else PORTB = PORTB & ~(1 << data);
//    PINB = 1 << clk;                      // clk high
//  }
//}

void init_display()
{

}

// Display off = 0, on = 1
void display_on(uint8_t on)
{

}

// Graphics **********************************************

// Clear display
void clear_display()
{
  tft.clearScreen();
  //  PINB = 1 << cs;                         // cs low
  //  Send(0x25);                             // Clear Window
  //  Send(0); Send(0); Send(95); Send(63);
  //  PINB = 1 << cs;                         // cs high
  //  delayMicroseconds(900);
  //delayMicroseconds(450);                 // half delay when clocking nano to 8 MHz
}

// Draw line to (x,y) in given colour
void draw_line (uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, uint16_t col)
{
  uint8_t red   = (col & 0xf800) >> 10;
  uint8_t green = (col & 0x07e0) >> 5;
  uint8_t blue  = (col & 0x001f) << 1;

  tft.drawLine(x0, y0, x1, y1, col);
  //  PINB = 1 << cs;                         // cs low
  //  Send(0x21);                             // Draw Line
  //  Send(x0); Send(y0); Send(x1); Send(y1);
  //  Send(red); Send(green); Send(blue);
  //  PINB = 1 << cs;                         // cs high
}

// Plot a point at (x,y)
void plot_point (uint8_t x, uint8_t y, uint16_t col)
{
  draw_line(x, y, x, y, col);
}



// Plot character in given colour
void plot_char (uint8_t x, uint8_t y, uint8_t ch, uint16_t col, uint8_t scale)
{
  uint8_t red   = (col & 0xf800) >> 10;
  uint8_t green = (col & 0x07e0) >> 5;
  uint8_t blue  = (col & 0x001f) << 1;

  //  PINB = 1 << cs;                         // cs low
  for (uint8_t c = 0 ; c < 5; c++) {      // Column range
    uint8_t bits = pgm_read_byte(&font[ch - 32][c]);
    uint8_t r = 0;
    while (bits) {
      while ((bits & 1) == 0) {
        r++;
        bits = bits >> 1;
      }
      uint8_t on = (7 - r) * scale;
      while ((bits & 1) != 0) {
        r++;
        bits = bits >> 1;
      }
      uint8_t off = (7 - r) * scale + 1;
      for (int i = 0; i < scale; i++) {
        uint8_t h = x + c * scale + i;
        //        Send(0x21);                         // Draw line
        //        Send(h); Send(y + on); Send(h); Send(y + off);
        //        Send(red); Send(green); Send(blue);
      }
    }
  }
  //  PINB = 1 << cs;                           // cs high
}

// Plot text
void plot_text(uint8_t x, uint8_t y, const char* text, uint16_t col, uint8_t scale)
{
  while (1) {
    char c = *(text++);
    if (c == 0) return;
    plot_char(x, y, c, col, scale);
    x += 6;
  }
}

// Higher level functions for drawing ui elements

// convert unsigned byte to char array
// *s points to end() of buffer
char *btoa(uint8_t x, char *s)
{
  *--s = 0;
  if (!x) *--s = '0';
  for (; x; x /= 10)
    * --s = '0' + x % 10;
  return s;
}


// Master volume: -10 db - +4 db
// Mix: 0 - 50% - 100% (Not all values are possible, map to pot values)
// Valid values for dir are -1 and 1


// Draw volume -15 ------ 0 --- +4db / Mute
// Values given are 0-20.
void draw_volume(const uint16_t* palette, const uint16_t bg, const uint8_t pos, const uint8_t val)
{
  uint8_t start = pos * 9 + 1;
  uint8_t len = map(val, 0, 20, 0, 238);
  uint16_t barcol  = palette[0];
  uint16_t textcol = palette[3];

  tft.fillRect(1 , start, 238, 8, bg);        // Erase old bar
  tft.fillRect(1 , start, len, 8, barcol);   // Draw new bar

  //  plot_text(2, start, "Vol", textcol);
  tft.setCursor(2, start);
  tft.setTextColor(textcol);
  tft.setTextSize(1);
  tft.print("Vol");

  // The code for constructing these on the fly
  // takes as much space, but the array is simpler.
  static const char db[21][4] = {"-80",
                                 "-16", "-15", "-14", "-13",
                                 "-12", "-11", "-10", " -9",
                                 " -8", " -7", " -6", " -5",
                                 " -4", " -3", " -2", " -1",
                                 " +0", " +1", " +2", " +3"
                                };

  //plot_text(31, start, db[val], textcol);
  tft.setCursor(220, start);
  tft.setTextColor(textcol);
  tft.setTextSize(1);
  tft.print(db[val]);

  //  plot_text(49, start, "db", textcol);
  tft.setCursor(115, start);
  tft.setTextColor(textcol);
  tft.setTextSize(1);
  tft.print("db");
}

// Draw mix as Dry ---  50% ---- Wet
// Values given are 0-20.
void draw_mix(const uint16_t* palette, const uint16_t bg, const uint8_t pos, const uint8_t val)
{
  uint8_t start = pos * 9 + 1;
  uint8_t len = map(val, 0, 20, 0, 238);
  uint16_t barcol  = palette[0];
  uint16_t textcol = palette[3];

  tft.fillRect(1 , start, 238, 8, bg);        // Erase old bar
  tft.fillRect(1 , start, len, 8, barcol);   // Draw new bar

  //plot_text(2, start, "Dry", textcol);
  tft.setCursor(2, start);
  tft.setTextColor(textcol);
  tft.setTextSize(1);
  tft.print("Dry");


  //  plot_text(77, start, "Wet", textcol);
  tft.setCursor(220, start);
  tft.setTextColor(textcol);
  tft.setTextSize(1);
  tft.print("Wet");

  char buff[4];                  // 3 chars + \0
  buff[0] = buff[1] = ' ';       // put spaces in tens and hundreds position

  btoa(val * 5, buff + 4); // give pointer to just after buffer (end)

  //plot_text(37, start, buff, textcol);
  tft.setCursor(110, start);
  tft.setTextColor(textcol);
  tft.setTextSize(1);
  tft.print(buff);
}


//  Draws bar with name and value
// Parameter values shown as 0-100%
void draw_parameter(const uint16_t* palette, const uint16_t bg, const char* text, const uint8_t pos, const uint8_t val)
{
  uint8_t start = pos * 9 + 1;
  uint8_t len = map(val, 0, 255, 0, 238);
  uint8_t perc = map(val, 0, 255, 0, 100);
  uint16_t barcol  = palette[0];
  uint16_t textcol = palette[3];

  tft.fillRect(1 , start, 238, 8, bg);        // Erase old bar
  tft.fillRect(1 , start, len, 8, barcol);   // Draw new bar


  //plot_text(2, start, text, textcol);
  tft.setCursor(2, start);
  tft.setTextColor(textcol);
  tft.setTextSize(1);
  tft.print(text);

  char buff[4];                  // 3 chars + \0 // add percent mark?
  buff[0] = buff[1] = ' ';       // put spaces in tens and hundreds position

  btoa(perc, buff + 4); // give pointer to just after buffer (end)

  // we want right justified, give buff instead of res
  //  plot_text(77, start, buff, textcol);
  tft.setCursor(220, start);
  tft.setTextColor(textcol);
  tft.setTextSize(1);
  tft.print(buff);
}

Do you have a question?

changing the nine to 100 (pos * 100 + 1) gives me this, so the nine can't be pixel height

9 represents the number of pixels moved in the y direction. The font takes up 7 pixels and in addition there are two blank lines. The size of the pixels are changed by the setTextSize command.
G

Looks like the library reduces your coordinates modulo 240.

Pixel height is normally one.

Sorry, “THE pixel height” can’t be the 9

I’m trying to figure out how each menu item has the same y position, yet they are spaced apart

maybe this will help you figure it out:

Nope

Then maybe you should create a very simple sketch to play with...

start = pos * 9 + 1;
tft.fillRect(1 , start, 238, 8, someColor);

...using various values of pos (say, from 0 to 5)

I meant each item has the same x and y position, but they’re in a list rather than on top of one another, I’m past it now anyway, thanks for the suggestions