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.
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.
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;
}
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.
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...
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;
}