Rotary encoder library slow - Stella Synth

Hi, I am following the Stella Synth tutorial from Arduino for Musicians. I have upoaded the code but the code value response to the rotary encoder is very slow and isn't in sync. It doesn't accelerate when then encoder is turned faster. It's in sync when turning slowly at a speed of 1 per second. When I test the rotary encoders with another interuppt example witj serial.print they respond immediately.

I have a feeling this is something to do with the interrupts being used in the code.

Main code calling MozziEncoder handler


//These encoders handle oscillator editing
MozziEncoder   wave1SelectionEncoder;
MozziEncoder   wave1OctaveEncoder;
MozziEncoder   wave1TuningEncoder;
MozziEncoder   wave2SelectionEncoder;
MozziEncoder   wave2OctaveEncoder;
MozziEncoder   wave2TuningEncoder;

     //Set up encoders for oscillator editing
     wave1SelectionEncoder.init(OSCENCODERB, OSCENCODERA, 0, 0, 0, 1);
     wave1OctaveEncoder.init(OSCENCODERB, OSCENCODERA, 0, 0, -4, 5);
     wave1TuningEncoder.init(OSCENCODERB, OSCENCODERA, 0, 0, 0, 100);
     wave2SelectionEncoder.init(OSCENCODERB, OSCENCODERA, 0, 0, 0, 1);
     wave2OctaveEncoder.init(OSCENCODERB, OSCENCODERA, 0, 0,-4, 5);
     wave2TuningEncoder.init(OSCENCODERB, OSCENCODERA, 0, 0, 0, 100);

MozziEncoder code

#include "MozziGuts.h"
#include "MozziEncoder.h"

MozziEncoder::MozziEncoder()
{
    m_lastEventTime=0;   //Only need to initialize this variable (other parameters
                         //will be set with init() method).
}


void MozziEncoder::init(int pinA, int pinB, int debounce_milliseconds,
                          int start_position, int min_position, 
						  int max_position, int _increment)
{
    m_pinA = pinA;
    m_pinB = pinB;
    m_debounceMS = debounce_milliseconds;
  //  pinMode(m_pinA, INPUT_PULLUP);
  //  pinMode(m_pinA, HIGH);  //turn on internal pullup
  //  pinMode(m_pinB, INPUT_PULLUP);
  //  pinMode(m_pinB, HIGH);  //turn on internal pullup
    m_lastEventTime = 0;
    m_encoderPosition = start_position;
    m_encoderMin = min_position;
    m_encoderMax = max_position;
    m_increment = _increment;
}

int MozziEncoder::trackShaftPosition()
{
    boolean pinAValue = digitalRead(m_pinA);
    unsigned long current_time = audioTicks();
    unsigned long time_between_events = current_time - m_lastEventTime;
    //Based on an example from Arduino Cookbook p. 191
    if((m_encoderALast == HIGH) && (pinAValue == LOW) )// && time_between_events > m_debounceMS)
    {
        if(digitalRead(m_pinB) == LOW)
        {
            if(m_encoderPosition - m_increment >= m_encoderMin)
                m_encoderPosition -= m_increment;
        }else{
            if(m_encoderPosition + m_increment <= m_encoderMax)
                m_encoderPosition += m_increment;
        }
        m_lastEventTime = current_time;
    }
    
    m_encoderALast = pinAValue;
    return m_encoderPosition;
}

so all your encoders sharethe same 2 pins = OSCENCODERB, OSCENCODERA? does not make much sense...

I don't know that library but you can't expect fantastic performance at high speed if they use digitalRead()

I use the encoder library

There are two encoders: one for oscillator and one for LFO. I was just showing code for the oscillator function. I didn't write this code. It is from Arduino for Musicians. The Encoders work fine, the code response is just slow.

here is the pin definition:

#define OSCENCODERA 2
#define OSCENCODERB 3
#define OSCPUSHPIN 4
#define LFOENCODERA 5
#define LFOENCODERB 6
#define LFOPUSHPIN 7

but only OSCENCODERB, OSCENCODERA are used in the init() calls...

I just shared the oscillator snippet of code to illustrate this. I tried the Encoder example you shared and the encoders are working perfectly responsively with serial.print.

Here is the complete main code if you'd like to see it. As I said before the encoder software is working the response is just very slow.

Main code

/******************************************************************* 
   Arduino for Musicians
   Brent Edstrom, 2015: REVISED 3/2018
   Fixed an issue with filter distortion
   
   FILE: StellaSynth9
   
   DESCRIPTION: 2-oscillator monophonic digital/analog synthesizer
   Based on Tim Barrass' Mozzi sound synthesis library for Arduino
   
*******************************************************************/

#include <MozziGuts.h>
#include <Oscil.h> // oscillator template

//Table data
#include <tables/triangle_valve_2048_int8.h> 
#include <tables/saw2048_int8.h>
#include <tables/sin2048_int8.h> 

//Mscl. header files
#include <LowPassFilter.h>  
#include <RollingAverage.h>
#include <mozzi_midi.h>
#include <AutoMap.h>
// #include <SoftwareSerial.h>
// #include "Serial7Segment.h"

// #include <Arduino.h>
#include <TM1637TinyDisplay.h>

#include "EasyMozziButton.h"
#include "MozziEncoder.h"
#include <MIDI.h>

MIDI_CREATE_DEFAULT_INSTANCE();

//LFO waveform
Oscil <SIN2048_NUM_CELLS, AUDIO_RATE> lfo(SIN2048_DATA);

//OSC1 waveforms:
Oscil<TRIANGLE_VALVE_2048_NUM_CELLS, AUDIO_RATE> osc1a(TRIANGLE_VALVE_2048_DATA);
Oscil<SAW2048_NUM_CELLS, AUDIO_RATE> osc1b(SAW2048_DATA);

//OSC2 waveforms:
Oscil<TRIANGLE_VALVE_2048_NUM_CELLS, AUDIO_RATE> osc2a(TRIANGLE_VALVE_2048_DATA);
Oscil<SAW2048_NUM_CELLS, AUDIO_RATE> osc2b(SAW2048_DATA);

//Enumeration to track active waveform
enum{triangle, saw};

// use #define for CONTROL_RATE, not a constant
#define CONTROL_RATE 128 // use powers of 2 

//Set up 7-segment serial display which takes a pointer
//to an instance of SoftwareSerial. 
// #define DISPLAYPINTX  8
// SoftwareSerial softwareSerial(11, DISPLAYPINTX);  //11 not used
// Serial7Segment display(&softwareSerial);

#define CLK 11
#define DIO 12

TM1637TinyDisplay display(CLK, DIO);

//Pin definitions
#define MIDIINPIN    0
#define OSCENCODERA  2
#define OSCENCODERB  3
#define OSCPUSHPIN   4
#define LFOENCODERA  5
#define LFOENCODERB  6
#define LFOPUSHPIN   7
#define LPCUTOFF     A0
#define LPRESONANCE  A1
#define LFORATE      A2
#define LFODEPTH     A3
#define AMPLEVEL     A4

//Create instances of a low pass filter
LowPassFilter lp_filter;

//Create button and encoder objects
EasyMozziButton  oscPushButton;
MozziEncoder     lfoEncoder;
EasyMozziButton  lfoPushButton;

//These encoders handle oscillator editing
MozziEncoder   wave1SelectionEncoder;
MozziEncoder   wave1OctaveEncoder;
MozziEncoder   wave1TuningEncoder;
MozziEncoder   wave2SelectionEncoder;
MozziEncoder   wave2OctaveEncoder;
MozziEncoder   wave2TuningEncoder;

//Set up a pointer that will point to the active wave encoder
MozziEncoder * pOscEncoder;

//Variables to track potentiometers
int ampLevel = 100;
int depth = 0;
int rate = 1;

//Globals for audio calculation
int osc1_wave_selection = triangle;
int osc1_octave_offset = 0;
int osc1_tune_offset = 0;
int osc2_wave_selection = triangle;
int osc2_octave_offset = 0;
int osc2_tune_offset = 0;
float current_osc1_frequency;  
float current_osc2_frequency; 
byte current_midi_note = 60;

//Tip: set note_on = true for testing sans MIDI input 
boolean note_on = false;                                   

//Set up auto map
AutoMap cutoffMap(0, 1023, 0, 255);
AutoMap resonanceMap(0, 1023, 0, 255);
AutoMap volumeMap(0, 1023, 0, 255);
AutoMap rateMap(0, 1023, 0, 2000);
AutoMap depthMap(0, 1023, 0, 5);  
AutoMap cvToCutoff(0, 1250, 0, 255);
AutoMap cvToResonance(0, 1250, 0, 255);
AutoMap cvToAmplitude(0, 1250, 0, 255);


//Enumeration for oscillator editing 
enum{osc1_wave, osc1_octave, osc1_tuning, osc2_wave, osc2_octave, osc2_tuning};
int osc_selection;

//Enumeration for LFO editing
enum{lfo_off, lfo_amplitude, lfo_cutoff, lfo_resonance, lfo_cutoff_and_resonance, lfo_frequency};
int lfo_selection;

void setup()
{  
     //Set up MIDI
     MIDI.setHandleNoteOff(handleNoteOff);
     MIDI.setHandleNoteOn(handleNoteOn);
     
     //Start MIDI at standard baud rate of 31250;
     MIDI.begin();
    
     //Initialize display
    //  display.setBrightness(255);
    // display.clearDisplay();
    // display.showString("STEL");
    // delay(1000);          //Wait a second before starting Mozzi
    
      display.clear();
      display.showString("STEL");
      delay(1000);

     osc_selection = osc1_wave;
     //Point to the selection encoder
     pOscEncoder = &wave1SelectionEncoder;
     lfo_selection = lfo_off;
     
     startMozzi(CONTROL_RATE); // set control rate
     setFrequencyFromMidiNote(current_midi_note);
    
     oscPushButton.init(OSCPUSHPIN, 5000);
     lfoEncoder.init(LFOENCODERB, LFOENCODERA, 0, 50, 1, 100);
     lfoPushButton.init(LFOPUSHPIN, 5000);
     
     //Set up encoders for oscillator editing
     wave1SelectionEncoder.init(OSCENCODERB, OSCENCODERA, 0, 0, 0, 1);
     wave1OctaveEncoder.init(OSCENCODERB, OSCENCODERA, 0, 0, -4, 5);
     wave1TuningEncoder.init(OSCENCODERB, OSCENCODERA, 0, 0, 0, 100);
     wave2SelectionEncoder.init(OSCENCODERB, OSCENCODERA, 0, 0, 0, 1);
     wave2OctaveEncoder.init(OSCENCODERB, OSCENCODERA, 0, 0,-4, 5);
     wave2TuningEncoder.init(OSCENCODERB, OSCENCODERA, 0, 0, 0, 100); 

     //TEST: Simulate a note-on message:
     handleNoteOn(1, 60, 100); 
     
}

void updateControl()
{
    //Check for MIDI input
    MIDI.read();


    //==========READ THE POTENTIOMETERS
    int cutoff = mozziAnalogRead(LPCUTOFF);
    lp_filter.setCutoffFreq(cutoffMap(cutoff));
       
    int res = mozziAnalogRead(LPRESONANCE);
    lp_filter.setResonance(resonanceMap(res)); 
       
    ampLevel = mozziAnalogRead(AMPLEVEL);
    ampLevel = volumeMap(ampLevel);
    
    rate = mozziAnalogRead(LFORATE);
    rate = rateMap(rate);
    lfo.setFreq(rate);
    
    depth = mozziAnalogRead(LFODEPTH);
    depth = depthMap(depth);
       
    //==========HANDLE THE LFO
    //Calculate control voltage amount
    
    long control_voltage = lfo.next() * depth;
    
    if(control_voltage && lfo_selection != lfo_off)
    {   
        //Respond to control voltage depending on current lfo selection
        if(lfo_selection == lfo_frequency)
        {
             int attenuated_voltage = control_voltage >> 1;
             //Update oscillators
             osc1a.setFreq(current_osc1_frequency + attenuated_voltage);
             osc1b.setFreq(current_osc1_frequency + attenuated_voltage);
             osc2a.setFreq(current_osc2_frequency + attenuated_voltage);
             osc2b.setFreq(current_osc2_frequency + attenuated_voltage);
        }
        
        if(lfo_selection == lfo_cutoff)
        {
              lp_filter.setCutoffFreq(cvToCutoff(control_voltage));
        }
        
        if(lfo_selection == lfo_resonance)
        {
              lp_filter.setResonance(cvToResonance(control_voltage));
        }
        
        if(lfo_selection == lfo_amplitude)
        {
              ampLevel = cvToAmplitude(control_voltage);
        }
      
    }
    
    //==========CHECK THE PUSHBUTTONS
    if(oscPushButton.checkButtonPressed())
    {
        oscSelectBtn();
    }
     if(lfoPushButton.checkButtonPressed())
    {
        lfoSelectBtn();
    }
    
   //Use a pointer to simmplify oscillator editing
   int last_value = pOscEncoder->getValue();
   if(pOscEncoder->trackShaftPosition() != last_value)
   {
      //Call encoder change function
      onOscValueChange();
      //Display the value:
      display.showNumber(pOscEncoder->getValue());
      delay(1000);
   }
   
   //override amplitude settings if no note is currently playing
   if(note_on == false)
   {
      ampLevel = 0; 
   }
    
}

void setFrequencyFromMidiNote(unsigned char note)
{
    // simple but less accurate
    current_osc1_frequency = mtof(note + (osc1_octave_offset * 12)); 
    current_osc2_frequency = mtof(note + (osc2_octave_offset * 12));
    
    //Add the tuning offsets...scale by 0.5 for more precise tuning
    current_osc1_frequency += (float)osc1_tune_offset * 0.5; 
    current_osc2_frequency += (float)osc2_tune_offset * 0.5;
       
    //update the oscillators   
    osc1a.setFreq(current_osc1_frequency);
    osc1b.setFreq(current_osc1_frequency);
    osc2a.setFreq(current_osc2_frequency);
    osc2b.setFreq(current_osc2_frequency);
    current_midi_note = note;
}

int updateAudio()
{
   //Update OSC1 waveforms
   char osc1a_sample = osc1a.next();
   char osc1b_sample = osc1b.next();
   
   //Update OSC2 waveforms
   char osc2a_sample = osc2a.next();
   char osc2b_sample = osc2b.next();
   
   //Grab the appropriate sample for processing
   char osc1_sample = osc1b_sample;
   if(osc1_wave_selection == triangle)
   {
       osc1_sample = osc1a_sample;
   }
   
   char osc2_sample = osc2b_sample;
   if(osc2_wave_selection == triangle)
   {
       osc2_sample = osc2a_sample;
   }
   
   //Combine the samples and attenuate, then multiply by ampLevel 
   //and attenuate again
   char signal = (lp_filter.next((osc1_sample+osc2_sample)>>2) * 
                 ampLevel) >> 8;
   
   //return the processed sample
   return (int)signal;
}


void loop()
{
   audioHook(); // required here
}


void oscSelectBtn()
{  
    osc_selection++;
    //Wrap around to first edit mode if necessary
    if(osc_selection > osc2_tuning)
    {
       osc_selection = osc1_wave; 
    }
   
   //Update pointer and print display current edit mode
   switch(osc_selection)
   {
      case osc1_wave:    
        pOscEncoder = &wave1SelectionEncoder;
        display.showString("OSC1");   
        break;
      case osc1_octave:  
        pOscEncoder = &wave1OctaveEncoder;
        display.showString("OCT1");   
        break;
      case osc1_tuning:  
        pOscEncoder = &wave1TuningEncoder;
        display.showString("TUN1");   
        break;
      case osc2_wave:    
        pOscEncoder = &wave2SelectionEncoder;
        display.showString("OSC2");   
        break;
      case osc2_octave:  
        pOscEncoder = &wave2OctaveEncoder;
        display.showString("OCT2");   
        break;
      case osc2_tuning:  
        pOscEncoder = &wave2TuningEncoder;
        display.showString("TUN2");   
        break;
   }

}

void onOscValueChange()
{
   //Update global variables from encoder values
   switch(osc_selection)
   {
      case osc1_wave:    
        osc1_wave_selection = wave1SelectionEncoder.getValue();  
        break;
      case osc1_octave: 
        osc1_octave_offset = wave1OctaveEncoder.getValue();  
        break;
      case osc1_tuning: 
        osc1_tune_offset = wave1TuningEncoder.getValue(); 
        break;
      case osc2_wave:    
        osc2_wave_selection = wave2SelectionEncoder.getValue();
        break;
      case osc2_octave: 
        osc2_octave_offset = wave2OctaveEncoder.getValue();  
        break;
      case osc2_tuning:  
        osc2_tune_offset = wave2TuningEncoder.getValue();  
        break;
   }
   
   //Update frequency
   setFrequencyFromMidiNote(current_midi_note);
}

void lfoSelectBtn()
{
   lfo_selection++;
   //Wrap to first LFO edit mode if necessary
   if(lfo_selection > lfo_frequency)
   {
      lfo_selection = lfo_off; 
   }
   
   //Display current LFO edit mode
   switch(lfo_selection)
   {
      case lfo_off:        display.showString("OFF");   break;
      case lfo_cutoff:     display.showString("CUT");   break;
      case lfo_resonance:  display.showString("RES");   break;
      case lfo_amplitude:  display.showString("VOL");   break;
      case lfo_frequency:  display.showString("FREQ");  break;
   }
   
   //Reset frequency if changing from frequency modulation
   if(lfo_selection != lfo_frequency)
   {
      //Update oscillators
      osc1a.setFreq(current_osc1_frequency); 
      osc1b.setFreq(current_osc1_frequency);
      osc2a.setFreq(current_osc2_frequency); 
      osc2b.setFreq(current_osc2_frequency); 
   }
}

//==========MIDI handler functions:
void handleNoteOff(byte channel, byte note, byte velocity)
{
   //Avoid turning off sound if another note is playing
   if(note == current_midi_note)
   {
       ampLevel = 0;
       note_on = false;
   }
}

void handleNoteOn(byte channel, byte note, byte velocity)
{
    current_midi_note = note;
    setFrequencyFromMidiNote(current_midi_note);
    note_on = true;
    //Display the current MIDI note
    display.showNumber(note);
    
    //Some manufacturers use Note-On with velocity = 0 for Note-Off
    if(velocity == 0 && note == current_midi_note)
    {
       ampLevel = 0; 
       note_on = false;
    }
}

MozziEncoder

#include "MozziGuts.h"
#include "MozziEncoder.h"

MozziEncoder::MozziEncoder()
{
    m_lastEventTime=0;   //Only need to initialize this variable (other parameters
                         //will be set with init() method).
}


void MozziEncoder::init(int pinA, int pinB, int debounce_milliseconds,
                          int start_position, int min_position, 
						  int max_position, int _increment)
{
    m_pinA = pinA;
    m_pinB = pinB;
    m_debounceMS = debounce_milliseconds;
  //  pinMode(m_pinA, INPUT_PULLUP);
  //  pinMode(m_pinA, HIGH);  //turn on internal pullup
  //  pinMode(m_pinB, INPUT_PULLUP);
  //  pinMode(m_pinB, HIGH);  //turn on internal pullup
    m_lastEventTime = 0;
    m_encoderPosition = start_position;
    m_encoderMin = min_position;
    m_encoderMax = max_position;
    m_increment = _increment;
}

int MozziEncoder::trackShaftPosition()
{
    boolean pinAValue = digitalRead(m_pinA);
    unsigned long current_time = audioTicks();
    unsigned long time_between_events = current_time - m_lastEventTime;
    //Based on an example from Arduino Cookbook p. 191
    if((m_encoderALast == HIGH) && (pinAValue == LOW) )// && time_between_events > m_debounceMS)
    {
        if(digitalRead(m_pinB) == LOW)
        {
            if(m_encoderPosition - m_increment >= m_encoderMin)
                m_encoderPosition -= m_increment;
        }else{
            if(m_encoderPosition + m_increment <= m_encoderMax)
                m_encoderPosition += m_increment;
        }
        m_lastEventTime = current_time;
    }
    
    m_encoderALast = pinAValue;
    return m_encoderPosition;
}

I don't know what to say

the init function for the MozziEncoder class takes the 2 pins for the encoder

and your encoders instances

seems to all use the same 2 parameters for those pins - ie OSCENCODERB and OSCENCODERA which are defined as

so are all the encoders physically connected to pin 2 and 3?

this seems weird.

No:
encoder 1 for OSC is on: pins 2,3,4 - btn
encoder 2 for LFO is on: pins 5,6,7 - btn

#define OSCENCODERA 2
#define OSCENCODERB 3
#define OSCPUSHPIN 4
#define LFOENCODERA 5
#define LFOENCODERB 6
#define LFOPUSHPIN 7

As I said all this code works fine. It's something to do with the MozziEncoder function.

I added serial. print to the MozziEncoder function and it printed out symbols as equally slow as the LCD readout. So I think it's something to with this code, or how it is called and interrupts.

    if((m_encoderALast == HIGH) && (pinAValue == LOW) && time_between_events > m_debounceMS)
    {
        if(digitalRead(m_pinB) == LOW)
        {
        Serial.println(m_encoderPosition);
            if(m_encoderPosition - m_increment >= m_encoderMin)
                m_encoderPosition -= m_increment;
        }else{
        Serial.println(m_encoderPosition);
            if(m_encoderPosition + m_increment <= m_encoderMax)
                m_encoderPosition += m_increment;
        }
        m_lastEventTime = current_time;
    }

I won't argue but the encoders wave1SelectionEncoder, wave1OctaveEncoder, wave1TuningEncoder, wave2SelectionEncoder, wave2OctaveEncoder and wave2TuningEncoder all point to pin 2 and 3.
that's in the code...


there are no interrupt in the code you provided

can you post the full code

Yes, they are menu options for the OSC encoder. 1 encoder, 6 menu options. I could see how the language might have confused you but they are calling menu options as below.

I posted the full code above:

/Update pointer and print display current edit mode
   switch(osc_selection)
   {
      case osc1_wave:    
        pOscEncoder = &wave1SelectionEncoder;
        display.showString("OSC1");   
        break;
      case osc1_octave:  
        pOscEncoder = &wave1OctaveEncoder;
        display.showString("OCT1");   
        break;
      case osc1_tuning:  
        pOscEncoder = &wave1TuningEncoder;
        display.showString("TUN1");   
        break;
      case osc2_wave:    
        pOscEncoder = &wave2SelectionEncoder;
        display.showString("OSC2");   
        break;
      case osc2_octave:  
        pOscEncoder = &wave2OctaveEncoder;
        display.showString("OCT2");   
        break;
      case osc2_tuning:  
        pOscEncoder = &wave2TuningEncoder;
        display.showString("TUN2");   
        break;
   }

}

void onOscValueChange()
{
   //Update global variables from encoder values
   switch(osc_selection)
   {
      case osc1_wave:    
        osc1_wave_selection = wave1SelectionEncoder.getValue();  
        break;
      case osc1_octave: 
        osc1_octave_offset = wave1OctaveEncoder.getValue();  
        break;
      case osc1_tuning: 
        osc1_tune_offset = wave1TuningEncoder.getValue(); 
        break;
      case osc2_wave:    
        osc2_wave_selection = wave2SelectionEncoder.getValue();
        break;
      case osc2_octave: 
        osc2_octave_offset = wave2OctaveEncoder.getValue();  
        break;
      case osc2_tuning:  
        osc2_tune_offset = wave2TuningEncoder.getValue();  
        break;
   }

OK they are really defined as 6 MozziEncoder instances...

and the code uses them to benefit from various config parameters I suppose. Not the optimal use of memory but it probably works out.

I see this in the code:

  if (pOscEncoder->trackShaftPosition() != last_value)
  {
    //Call encoder change function
    onOscValueChange();
    //Display the value:
    display.showNumber(pOscEncoder->getValue());
    delay(1000);
  }

so when the encoder changes value, you pause the whole code for 1 second...

OMG. What a coincidence. I just spotted this myself stripping out the code and changed to delay(100). Thanks for looking :slight_smile:

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.