Skip to content
Snippets Groups Projects
Select Git revision
  • b36d3d76de0a2003b1d917cbf90a47eb6b306ebc
  • main default protected
  • fix-buffer
  • feature-individual-lfos
  • feature-individual-volume
  • 1.3.0
  • 1.2.0
  • 1.1.0
  • 1.0.1
  • 1.0.0
10 results

potentiometers.h

Blame
  • David Huss's avatar
    David Huss authored
    The Arduino IDE wants things to be in a folder that has the same name as
    the ino. Lets do that then.
    b36d3d76
    History
    potentiometers.h 8.04 KiB
    #include "WCharacter.h"
    #include "wiring_analog.h"
    #include <stdint.h>
    #include <limits>
    #ifndef Potentiometers_h
    #define Potentiometers_h
    #include "Arduino.h"
    #include "luts.h"
    #include "helpers.h"
    
    #include "Adafruit_SH110X.h"
    #include "Adafruit_GFX.h"
    extern Adafruit_SH1106G display;
    
    // The length of the moving average filter that smooths the
    // controls. Higher number is smoother, but less responsive
    // and needs more memory.
    #define POT_MOVING_AVERAGE_SIZE 2
    
    // Length of the Textbuffer for floats in the UI
    #define UI_TEXTBUFFER_LENGTH 6
    
    // Modes
    enum PotMode {
      POT_MODE_LIN,
      POT_MODE_BIP,
      POT_MODE_PITCH,
      POT_MODE_SWITCH,
      POT_MODE_LAST
    };
    
    // Display Modes
    enum PotDisplayMode {
      POT_DISPLAY_MODE_DEFAULT,
      POT_DISPLAY_MODE_PITCH,
      POT_DISPLAY_MODE_PERCENT,
      POT_DISPLAY_MODE_SWITCH,
      POT_DISPLAY_MODE_SWITCH_NUMBERS,
      POT_DISPLAY_MODE_LAST
    };
    
    typedef void (*callback_function)(void);
    
    class Potentiometer {
      int pin;
      int readings[POT_MOVING_AVERAGE_SIZE];
      PotMode mode = POT_MODE_LIN;
      PotDisplayMode display_mode = POT_DISPLAY_MODE_DEFAULT;
      float last_reading, last_normalized_reading = 0.0f;
      float display_scale = 1.0f;
      callback_function onChangeFunction;
      Easer easer;
    
      public:
        Potentiometer(int pin);
        void init();
        void setLinear();
        void setPitch();
        void setSwitch();
        float read();
        void setOnChange(callback_function f);
        void renderUi();
        void setDisplayMode(const char *name, float display_scale, PotDisplayMode display_mode);
        const char *name;
        double last_displayed = 0.0;
        bool should_display = false;
        bool display_value_changes = false;
        bool last_was_nan = false;
        uint8_t switch_positions;
        uint8_t switch_offset = 0;
        const char* const switch_labels[4] = {"TRI", "SQR", "RAND", "JUMP"};
    };
    
    Potentiometer::Potentiometer(int pin) {
      this->pin = pin;
    }
    
    void Potentiometer::init() {
      analogReadResolution(12);
      easer.setFactor(0.001);
    }
    
    void Potentiometer::setLinear() {
      this->mode = POT_MODE_LIN;
    }
    
    void Potentiometer::setPitch() {
      this->mode = POT_MODE_PITCH;
    }
    
    void Potentiometer::setSwitch() {
      this->mode = POT_MODE_SWITCH;
    }
    
    float Potentiometer::read() {
      int reading = analogRead(this->pin);
      // Shift all readings in the buffer over by one position, deleting the oldest
      // and adding the newest
      for (int i=0; i<POT_MOVING_AVERAGE_SIZE; i++) {
        int next = i+1;
        if (next < POT_MOVING_AVERAGE_SIZE) {
          (this->readings)[i] = (this->readings)[next];
        }
      }
      (this->readings)[POT_MOVING_AVERAGE_SIZE-1] = reading;
    
      // Get the average of the last readings
      reading = 0;
      for (int i=0; i<POT_MOVING_AVERAGE_SIZE; i++) {
        reading += (this->readings)[i];
      }
      reading = reading / POT_MOVING_AVERAGE_SIZE;
    
      // Convert the last reading to a float and return
      float current_reading = easer.Process(reading / 4096.0f);
      float normalized_reading = current_reading;
    
      // Depending on the Mode
      switch (this->mode) {
        case POT_MODE_PITCH:
          current_reading = get_from_xy_table(pitch_knob_lookup_x, pitch_knob_lookup_y, current_reading, pitch_knob_lookup_length);
          break;
        case POT_MODE_SWITCH:
          current_reading = int(current_reading * switch_positions);
          break;
      }
    
      bool changed = abs(normalized_reading - this->last_normalized_reading) > 0.002;
    
      // If the difference to the last reading is big enough assume the knob has been touched
      if (this->last_normalized_reading && changed) {
        if (display_value_changes) {
          last_displayed = millis();
          should_display = true;
        }
        if (this->onChangeFunction) { this->onChangeFunction(); }
      }
    
      if (this->last_normalized_reading && !changed) {
        // if (!last_was_nan) {
        //   Serial.print(this->name);
        //   Serial.println(" returned NaN");
        // }
        last_was_nan = true;
        return std::numeric_limits<float>::quiet_NaN();
      }
      last_was_nan = false;
      this->last_reading = current_reading;
      this->last_normalized_reading = normalized_reading;
      return current_reading;
    }
    
    void Potentiometer::setOnChange(callback_function f) {
      this->onChangeFunction = f;
    }
    
    void Potentiometer::setDisplayMode(const char *name, float display_scale, PotDisplayMode display_mode) {
      this->display_value_changes = true;
      this->name = name;
      this->display_scale = display_scale;
      this->display_mode = display_mode;
    }
    
    void Potentiometer::renderUi() {
      double now = millis();
        if (this->should_display) {
          int x_margin = 28;
          int y_margin = 13;
          int x_center = display.width()/2;
          int y_center = display.height()/2;
          // Render a rectangle Backdrop for the text
          display.fillRect(3+x_margin, 3+y_margin, display.width()-x_margin*2, display.height()-y_margin*2, SH110X_WHITE);
          display.fillRect(x_margin, y_margin, display.width()-x_margin*2, display.height()-y_margin*2, SH110X_WHITE);
          display.fillRect(x_margin+1, y_margin+1, display.width()-(x_margin+1)*2, display.height()-(y_margin+1)*2, SH110X_BLACK);
          
    
          // Render the name of the parameter (e.g. "Pitch")
          centeredText(this->name, x_center, y_center-4, SH110X_WHITE);
          
          // Choose how many digits to display depending on the mode
          int digits = 2;
          if (this->display_mode == POT_DISPLAY_MODE_PERCENT) { digits = 0; }
          // Allocate a buffer for the float and convert it into characters
          char value_buffer[UI_TEXTBUFFER_LENGTH]; // Buffer big enough for 7-character float
          dtostrf(this->last_reading*display_scale, 6, digits, value_buffer); // Leave room for too large numbers!
          
          // If we are on a bipolar pot display an indicator if we are in the center
          if (this->mode == POT_MODE_BIP && this->last_reading > -0.0001 && this->last_reading < 0.0001) {
            display.fillTriangle(x_center, y_center+10, x_center+3, y_center+15, x_center-3, y_center+15, SH110X_WHITE);
          }
    
          // If we are on a pitch pot display an indicator if we are in the the right steps
          if (this->mode == POT_MODE_PITCH) {
            float reading_mod = fmod(abs(this->last_reading), 0.05f);
            if (reading_mod > 0.999f || reading_mod < 0.001f) {
              display.fillTriangle(x_center, y_center+10, x_center+3, y_center+15, x_center-3, y_center+15, SH110X_WHITE);
            }
          }
    
          // The float value may contain some empty whitespace characters, remove them by
          // first figuring out which the first actual character is
          int nonwhite = 0;
          for (int i=0; i<UI_TEXTBUFFER_LENGTH; i++) {
            if (value_buffer[i] == ' ') {
              nonwhite++;
            }
          }
    
          // Create a new buffer that can hold everything
          char text_buffer[UI_TEXTBUFFER_LENGTH+6];
    
          // Copy all non-white characters over
          for (int i = 0; i<UI_TEXTBUFFER_LENGTH; i++) {
            text_buffer[i] = value_buffer[i+int(nonwhite)];
          }
    
          // Figure out where the last character (\0) in our new buffer is
          int last = UI_TEXTBUFFER_LENGTH+6;
          for (int i=16; i>0; i--) {
            if (text_buffer[i] == '\0') {
              last = i;
            }
          }
    
          // Add units depending on the display mode : )
          if (this->display_mode == POT_DISPLAY_MODE_PERCENT) {
            text_buffer[last] = ' ';
            text_buffer[last+1] = '%';
            text_buffer[last+2] = '\0';
          } else if (this->display_mode == POT_DISPLAY_MODE_PITCH) {
            text_buffer[last] = ' ';
            text_buffer[last+1] = 'S';
            text_buffer[last+2] = 'e';
            text_buffer[last+3] = 'm';
            text_buffer[last+4] = 'i';
            text_buffer[last+5] = '\0';
          }
    
          // Render that new text
          if (this->display_mode == POT_DISPLAY_MODE_SWITCH) {
            centeredText(switch_labels[int(last_reading+switch_offset)], x_center, y_center+4, SH110X_WHITE);
          } else if (this->display_mode == POT_DISPLAY_MODE_SWITCH_NUMBERS) {
            sprintf(text_buffer, "%d", int(last_reading));  
            centeredText(text_buffer, x_center, y_center+4, SH110X_WHITE);
          } else {
            centeredText(text_buffer, x_center, y_center+4, SH110X_WHITE);
          }
        }
    
        // Show this for 700 ms after it has been last touched
        if ((now - this->last_displayed) > 700.0) {
          this->should_display = false;
        }
    }
    
    
    #endif