DIYstompboxes.com

DIY Stompboxes => Digital & DSP => Topic started by: any on May 22, 2021, 11:32:07 PM

Title: Arduino tap tempo LED/LDR output w. 6 waveform presets, ramp up/down, etc...
Post by: any on May 22, 2021, 11:32:07 PM
Hi,

Been working my first forage into 'programmed' fx related things..
Partly based/inspired on a previous post (https://www.diystompboxes.com/smfforum/index.php?topic=121506.20) but with some added functionality.

I'm building this into a Line6 M5 which accepts a 10k pot as expression input.

So far, what it does is the following:
- tap tempo (working on syncing this up with Line6 internal tap)
- manual 'speed' override for tempo (also needed to set toe up/down settings on the line6 as it needs to 'see' a 10k resistance max on the expression input to recognise something)
- 6 selectable waveforms (6 position mini rotary) on(just max brightness), square, sine, ramp up, ramp down, random
- momentary or latching (not fully implemented in v1.0 code yet, just momentary for now, so effect on while foot is down, will implement a "double tap function" to switch between momentary or latching)
- When in momentary mode, a single tap will bring up the M5 preset menu. (only useful for Line6 M5/9 owners?)
- Ramp up and ramp down (Currently on all waveforms in v1.0 but will be selectable fast/slow/off)

Uses the Onebutton and JLed libraries so very easy to modify.

I'll post a slightly cleaned up version of current code tonight, but still working through the full feature set.
I have already tested the current version with a Line6 M5 and it sounds great!

More to come :-)
 
Title: Re: Arduino tap tempo LED/LDR output w. 6 waveform presets, ramp up/down, etc...
Post by: Blackaddr on May 23, 2021, 06:59:35 AM
Demo video?
Title: Re: Arduino tap tempo LED/LDR output w. 6 waveform presets, ramp up/down, etc...
Post by: niektb on May 23, 2021, 07:08:42 AM
Following! I have an M5 also and I'm working on a tiny midi controller 😋 (3 foot switches in a 1590A)
Title: Re: Arduino tap tempo LED/LDR output w. 6 waveform presets, ramp up/down, etc...
Post by: any on May 23, 2021, 07:43:09 AM
Yep, will do a demo when it is functionally complete.
Currently slightly difficult w. Breadboard,  trimpots, etc...
Title: Re: Arduino tap tempo LED/LDR output w. 6 waveform presets, ramp up/down, etc...
Post by: any on May 25, 2021, 06:12:59 AM
No demo yet, but finished a lot of the functionalities.

- Latching and Momentary option for engaging the LED/LDR ('single tap on/single tap off' for latching, 'longpress' for momentary)
- Double tap to switch (with indicator led) to an alternate mode where single tap is not latching the effect, but switches the Line6 M5/M9 presets. Longpress is still momentary effect.
- 6 Waveforms, but depending on your switch arrangement can be anything you like. ;-)
- Tap tempo with pot override
- Needs a 10 Pot inline with the LDR for the Line6 to recognise it, which doubles as and 'intensity' control (manually set fully clockwise (10) for full effect.)
- Ramp up/Ramp down, currently only on the 'always on' led as it's doesn't sound that good with modulated waveforms but works quite well.

Here's the code I got so far, if anyone wants to see if things can be simplified or cleaned up comments are welcome.
I've tried to add as much annotations as possible to explain what everything does.

V1.2
Code: [Select]
#include <OneButton.h> // The OneButton Library
#include <jled.h> // The Jled Library

OneButton effectbutton = OneButton(
  7,        // Input pin for the button
  true,     // Button is active LOW
  true      // Enable internal pull-up resistor
);

#define THRESHOLD 30    // Threshold for the 'tempo pot' to overtake the tapped tempo
#define EXP1 A0         // Tempo pot attached to pin A0
#define MAXDELAY 3000   // Maximum delay time in Msec that can be achieved with the tempo pot
#define MINDELAY 1      // Maximum delay time in Msec that can be achieved with the tempo pot
#define MAXTAP 8000      // Maximum delay time in Msec that can be achieved with the tap tempo

//Declaring the names of the waveforms and pins
/* This will vary on how you set up the waveform selection. I've used a 6 position rotary switch that has
two pairs of common pins at opposite sides having 4 contacts each.(bit of a weird setup, but that's what I had lying around)
Should also work with a standard 2 pin 6 position rotary dial wired to 3 digital inputs. Each has a unique combination
of pin states. Pins are high by default and pulled to ground to set. Based on what type of waveform selection solution
you come up with you can theoretically have x amount of options as Jled allows for 'user function' waveforms :-)*/
auto ledtempo = JLed(6);      // This Led is always on and provides visual tempo feedback
auto ledon = JLed(10);        // Technically just 'always on' so not really a 'waveform'
auto ledsquare = JLed(10);    // 50% on 50% off
auto ledsine = JLed(10);      // Smooth cycle between 0% and 100%
auto ledrampup = JLed(10);    // Smooth cycle up to 100% for the duration of the tap, then start over
auto ledrampdown = JLed(10);  // Start at 100% with a smooth cycle down to 0%, then start over
auto ledrandom = JLed(10);    // Based on the Jled 'candle' setting, basically random values with user setting for 'jitter'

int period = 2000;            // Default start time for the tempo
int lastVal = 0;
int tempAnalog = 0;
boolean PotControls = true;   // Allows the tempo pot to take over the tapped tempo

// Variables will change:

int buttonPushCounter = 1;    // counter for the number of button presses
int doubleClickCounter = 0;   // counter for the number of doubleclicks
int buttonState = 0;          // current state of the button
int lastButtonState = 0;      // previous state of the button

void setup()
{
 
  effectbutton.attachClick(effectsingleclick);                  // link the function to be called on a singleclick event.
  effectbutton.attachDoubleClick(effectdoubleclick);            // link the function to be called on a doubleclick event.
  effectbutton.attachLongPressStart(effectlongpressstart);      // link the function to be called at the start of a longpress event.
  effectbutton.attachLongPressStop(effectlongpressstop);        // link the function to be called at the end of a longpress event.
  effectbutton.attachDuringLongPress(effectlongpress);          // link the function to be called during a longpress event.
 
  effectbutton.setClickTicks(200);   // Time for registering single Click
  effectbutton.setPressTicks(500);   // Time for registering Long Press
 
  pinMode(2, INPUT_PULLUP );         // Used for waveform selection with a two pin, 6 position rotary dial
  pinMode(3, INPUT_PULLUP );         // Used for waveform selection with a two pin, 6 position rotary dial
  pinMode(4, INPUT_PULLUP );         // Used for waveform selection with a two pin, 6 position rotary dial
  pinMode(5, INPUT_PULLUP );         // tap button - press it to set the tempo
  pinMode(6, OUTPUT);                // This is the LED showing the current tempo
  digitalWrite(8, LOW);              // Make sure this starts low to not trigger the Line6 effect on/off button
  pinMode(8, OUTPUT);                // This goes to Line6 Effect On/Off button
  digitalWrite(9, LOW);              // Make sure this starts low to not trigger the Line6 tap button
  pinMode(9, OUTPUT);                // This goes to Line6 Tap button
  pinMode(10, OUTPUT);               // This goes to the LED/LDR that controls the expression pedal
  digitalWrite(13, LOW);             // Make sure this starts low at startup
  pinMode(13, OUTPUT);               // LED that shows when doubleclick function is active
 
  buttonPushCounter = 1;              // counter for the number of button presses
  doubleClickCounter = 0;             // counter for the number of double clicks
 
  // initialize serial communication (for debugging):
  Serial.begin(9600);
 
}
int lastTapState = LOW;  /* the last tap button state */
unsigned long currentTimer[2] = { 500, 500 };  /* array of most recent tap counts */

void loop()
{
  /* read the button on pin 5, and only pay attention to the
     HIGH-LOW transition so that we only register when the
     button is first pressed down */
  buttonState = digitalRead(7);
  int tapState = digitalRead( 5 );
  if( tapState == LOW && tapState != lastTapState )
    {
      tap(); /* we got a HIGH-LOW transition, call our tap() function */
    }
  lastTapState = tapState; /* keep track of the state */
 
  ledtempo.Update();    // update leds
  delay(1);
 
  effectbutton.tick();  // Check the effect button for click/doubleclick/longpress events
 
  poll_pot();           // check pot value

  /* If the effect is switched on in latching mode, toggle between continuously updating the waveform
  or incrementing the counter when switched off so it's ready for the next event*/
 
  if (buttonPushCounter %2 == 0) {
    update_effect(); }
  else {
      buttonPushCounter = 1;
    }
}

unsigned long lastTap = 0; /* when the last tap happened */

void tap()
{
  /* we keep two of these around to average together later */
  currentTimer[1] = currentTimer[0];
  currentTimer[0] = millis() - lastTap;
  lastTap = millis();
 
  period = ((currentTimer[0] + currentTimer[1])/2);   // establish new period

  PotControls = false;   // override the pot when tapping a tempo

  update_leds();   // update leds period
}

  void update_leds(){   // Update the led's tempo information after tapping or setting the tempo pot
 
  ledtempo = JLed(6).Blink(period/20, period/20*19).Forever(),
  ledon = JLed(10).On().Forever();
  ledsquare = JLed(10).Blink(period/2, period/2).Forever();
  ledsine = JLed(10).Breathe(period).DelayAfter(0).Forever();
  ledrampup = JLed(10).FadeOn(period).DelayBefore(0).Forever();
  ledrampdown = JLed(10).FadeOff(period).DelayBefore(0).Forever();
  ledrandom = JLed(10).Candle(period/286 /*speed*/, 255 /* jitter*/);
}

void update_effect(){   // Keep the led waveform updated in the loop until it's turned off
 
   if (digitalRead(2) == LOW && digitalRead(3) == HIGH && digitalRead(4) == HIGH) { // Always on LED is selected
      ledon.Update();
   }
    if (digitalRead(2) == HIGH && digitalRead(3) == LOW && digitalRead(4) == HIGH) { // Random LED is selected
      ledrandom.Update();
   }
    if (digitalRead(2) == LOW && digitalRead(3) == HIGH && digitalRead(4) == LOW) { // Square wave LED is selected
      ledsquare.Update();
   }
    if (digitalRead(2) == HIGH && digitalRead(3) == LOW && digitalRead(4) == LOW) { // Sine wave LED is selected
      ledsine.Update();
   }
    if (digitalRead(2) == HIGH && digitalRead(3) == HIGH && digitalRead(4) == LOW) { // Ramp up LED is selected
      ledrampup.Update();
   }
    if (digitalRead(2) == HIGH && digitalRead(3) == HIGH && digitalRead(4) == HIGH) { // Ramp down LED is selected
      ledrampdown.Update();
   }
 
}

void poll_pot() {                   // Allow tempo pot reading to override tapped tempo

  tempAnalog = analogRead(EXP1);    // Read analog value
 
  // Convert 10 bit to milliseconds, 3 seconds maximum
  tempAnalog = map(tempAnalog, 0, 1023, 0, MAXDELAY);
  tempAnalog = constrain(tempAnalog, MINDELAY, MAXDELAY);
 
  // Check pot value and compare variation with threshold
  if((abs(tempAnalog-lastVal)>THRESHOLD))
  {
    // update period with pot value
    period = tempAnalog;
    //return control to Pot   
    PotControls = true;
    // Store current value to be used laters
    lastVal = tempAnalog;
    // update leds with new timing
    update_leds();
  }
}


// effect button actions

void effectsingleclick(){
  buttonPushCounter++;      // every time the effect turns on, increment the counter
  if (buttonPushCounter %2 == 0 && doubleClickCounter == 0 ) {  // Determines if the effect is currently off and is not in the double click function

   effectlongpressstart();  // Run through longpressstart in case 'ramp up' function is selected
   update_effect();         // Run the chosen waveform until the effect is turned off
  }
/* If a doubleclick is active, make sure the led stays off and fire a momentary pulse
(in my case to a CD4066 cmos switch) to switch the Line6 effect and tap buttons   
simultenously to trigger the preset select.*/
 else if (doubleClickCounter != 0 ) { 
  ledon.Stop();
  ledrandom.Stop();
  ledsquare.Stop();
  ledsine.Stop();
  ledrampup.Stop();
  ledrampdown.Stop();
  digitalWrite(8, !digitalRead(8));         // This goes to Line6 Effect On/Off button
  digitalWrite(9, !digitalRead(9));        // This goes to Line6 Tap button
  delay(100);
  digitalWrite(8, !digitalRead(8));         // This goes to Line6 Effect On/Off button
  digitalWrite(9, !digitalRead(9));        // This goes to Line6 Tap button
  }
/* If there's no doubleclick active, another single click will go to longpresstop,
in case there is a 'ramp down' selected.*/
  else {
    effectlongpressstop();
  }
 
    }

 void effectdoubleclick(){
/* When a double click is detected increment the counter, otherwise reset the
counter to 0, effectively a toggle.*/
  if (doubleClickCounter %2 == 0) {
    doubleClickCounter++;
     Serial.print("double click on");
     Serial.println(doubleClickCounter);
  }
  else {
    doubleClickCounter = 0;
    Serial.print("reset the counter");
  }
  digitalWrite(13, !digitalRead(13));         // Toggle the indicator light for doubleclick on/off
}
   

void effectlongpressstart(){
  /* At the start of momentary or latching (they both cycle through longpresstart)
  Reset the LED so the timing starts from the moment you turn it on, and ramp up
  to the waveform if 'ramp up' is selected.*/
  ledon.Reset();
  ledrandom.Reset();
  ledsquare.Reset();
  ledsine.Reset();
  ledrampup.Reset();
  ledrampdown.Reset();
  // what happens when momentary effect engagement starts (ramp up)
 if (digitalRead(2) == LOW && digitalRead(3) == HIGH && digitalRead(4) == HIGH) { // if Always on LED is selected
  //Fading in the LED
  for(int i=0; i<255; i++){
    analogWrite(10, i);
    delay(period/64); //Increasing this value will give a longer ramp up time, can set to time in msec (10 default) or 'period/xxx' for tap related length
  }
}
}

void effectlongpress(){
  Serial.println(buttonState);

if (digitalRead(2) == LOW && digitalRead(3) == HIGH && digitalRead(4) == HIGH) { // Always on LED is selected
  ledon.Update();
}
if (digitalRead(2) == HIGH && digitalRead(3) == LOW && digitalRead(4) == HIGH) { // Random LED is selected
  ledrandom.Update();
}
if (digitalRead(2) == LOW && digitalRead(3) == HIGH && digitalRead(4) == LOW) { // Square wave LED is selected
  ledsquare.Update();
}
if (digitalRead(2) == HIGH && digitalRead(3) == LOW && digitalRead(4) == LOW) { // Sine wave LED is selected
  ledsine.Update();
}
if (digitalRead(2) == HIGH && digitalRead(3) == HIGH && digitalRead(4) == LOW) { // Ramp up LED is selected
  ledrampup.Update();
}
if (digitalRead(2) == HIGH && digitalRead(3) == HIGH && digitalRead(4) == HIGH) { // Ramp down LED is selected
  ledrampdown.Update();
}

}

void effectlongpressstop(){
    /* At the end of momentary or latching (they both cycle through longpresstop)
  Increment the counter, and ramp down if 'ramp down' is selected.
  Then turn off the LED completely with a digitalwrite as sometimes it kept glowing. :-) */
  buttonPushCounter = 1;

  // what happens when momentary effect engagement ends (ramp down)
 if (digitalRead(2) == LOW && digitalRead(3) == HIGH && digitalRead(4) == HIGH) { // Always On LED is selected
  for(int i=255; i>0; i--){
    analogWrite(10, i);
    delay(10);  //Increasing this value will give a longer ramp up time, can set to time in msec (10 default) or 'period/xxx' for tap related length
}
}
digitalWrite(10,LOW);  // Switch off the LED

}
Title: Re: Arduino tap tempo LED/LDR output w. 6 waveform presets, ramp up/down, etc...
Post by: ElectricDruid on May 25, 2021, 08:17:53 AM
Nice work. I think the code is very easy to read. It's very explicit about what it's doing and how, which is definitely a good thing!

There's one or two bits where I wondered if there might not be a more efficient way of doing it, but the "long hand" way you've done it is functional and clear. One example:

Code: [Select]
if (digitalRead(2) == LOW && digitalRead(3) == HIGH && digitalRead(4) == HIGH) { // Always on LED is selected
      ledon.Update();
   }
    if (digitalRead(2) == HIGH && digitalRead(3) == LOW && digitalRead(4) == HIGH) { // Random LED is selected
      ledrandom.Update();
   }
    if (digitalRead(2) == LOW && digitalRead(3) == HIGH && digitalRead(4) == LOW) { // Square wave LED is selected
      ledsquare.Update();
   }
    if (digitalRead(2) == HIGH && digitalRead(3) == LOW && digitalRead(4) == LOW) { // Sine wave LED is selected
      ledsine.Update();
   }
  // etc etc

This should definitely have "elseif"s for the subsequent statements, so the code doesn't have to test every single one. Only one waveform is selected at once, right?

I wondered if it wouldn't be possible to convert the digitalRead states to a integer and then do a Switch statement. I mean, the intent here is to read a three-bit binary number from the inputs, so all this logic testing stuff seems a bit excessive.

something like:

Code: [Select]
// Evaluate the binary value on the input and convert to 0-7
waveVal = 0;
if (digitalRead(2) == HIGH) { waveVal += 4; }
if (digitalRead(3) == HIGH) { waveVal += 2; }
if (digitalRead(4) == HIGH) { waveVal += 1; }
// It's a shame there isn't a neater way to do this, but I can't think of one without reading the port directly.

// Light the correct LED
switch (waveVal) {
case 0:
  // Light an LED
  ledsine.Update();
  break;
case 1:
  // Light an LED
  ledon.Update();
  break;
case 2:
  // etc etc
  break;
case 3:
  // etc etc
  break;
// etc etc

// You choose if you need this:
default:
  // statements
  break;
}

Whether this is an improvement is an open question. More a question of style, I'd say.
What I'd really like would be something like the following:

Code: [Select]
// Evaluate the binary value on the input and convert to 0-7
waveVal = 0;
if (digitalRead(2) == HIGH) { waveVal += 4; }
if (digitalRead(3) == HIGH) { waveVal += 2; }
if (digitalRead(4) == HIGH) { waveVal += 1; }

// Light the correct LED by passing the value to the function
UpdateLed(waveVal) {

The UpdateLed function is left as an exercise for the reader! I have no idea how you'd do that for Arduino code, sorry!
Title: Re: Arduino tap tempo LED/LDR output w. 6 waveform presets, ramp up/down, etc...
Post by: niektb on May 25, 2021, 09:29:04 AM
@the issue ElectricDruid raised. All the digitalReads definitely make it more difficult to read. Not using elseifs can also cause timing issues (like the digitalRead changing when it's in the body of an if-statement such that the body of a 2nd if-statement might also be executed).

Me personally, I would read the 3 ports once, convert them into a hex value using a bit shift operation, and test against that hex value :)
Title: Re: Arduino tap tempo LED/LDR output w. 6 waveform presets, ramp up/down, etc...
Post by: Firesledge on May 25, 2021, 12:21:06 PM
You could replace all the pin numbers with constants, too. The code will be more meaningful and this will ease pin number changes.
Title: Re: Arduino tap tempo LED/LDR output w. 6 waveform presets, ramp up/down, etc...
Post by: any on May 25, 2021, 04:04:44 PM
@ElectricDruid, @Niektb, @Firesledge Thanks For your comments ;-) Literally my first foray into Arduino, or programming for that matter, so plenty to learn... Yes I understand now how the waveform selection could be streamlined for both switching and updating the led.
Also, the Jled library has plenty of scope to add more useful waveforms quite easily, as well as Multi tap patterns. Might even try have 5 presets and one ‘manual’ waveshape with pot controls? ( Attack, decay, peak, etc)

I’ll work on this some more when I have some time, and probably do a ‘boxed’ version in an old project box so I can demo it with my M9 prior to building it in to the M5. I’m planning to leave the led/ldr socketed as well as the tempo pot on a connector so I can tweak/test on different effect units and circuits by swapping the ‘interface end’ of it.

Will try and do a demo soon ;-)
Title: Re: Arduino tap tempo LED/LDR output w. 6 waveform presets, ramp up/down, etc...
Post by: any on May 25, 2021, 11:40:25 PM
Bit of a demo with what it currently does. ( I haven't tweaked any presets specifically for this type of expression modulation)
I'll have more of a play when the tempo pot is useable and it's in some kind of enclosure.

Also thinking about a 'shift button' that will allow 12 waveform presets as I like the idea of a 'one shot' function
that does a ramp, sine, etc.. over a longer period (4x the tap time?) and then stops. anyway... you get the idea.

Title: Re: Arduino tap tempo LED/LDR output w. 6 waveform presets, ramp up/down, etc...
Post by: any on May 26, 2021, 06:14:13 PM
Demo video?

See previous post, not really optimised any patches on the M9, but should give you an idea.
Think I’ll implement a ‘bar-time’ function as well as i think it could sound cool if the rate takes a full bar (tempo x 4) for more sweeping type effects.
Title: Re: Arduino tap tempo LED/LDR output w. 6 waveform presets, ramp up/down, etc...
Post by: any on May 27, 2021, 07:24:17 AM
Cleaned up the code a bit more, and implemented the suggestions
(in this case replacing all pin numbers with constants and the way the presets are selected)

Thanks for the input so far, working well and working on a couple of patches on my M9 that show
some of the cool stuff you can do when you automate the expression pedal.  :icon_biggrin:
Imma calling it "Tap Express" for now, as it sounds kinda nice.

Thinking hard about what is most useful vs complexity, so still working on that.
Need to implement something that cancels the ramp up function if momentary is released or latching is pressed again while it's still ramping up.
Currently there's no interrupt so it'll keep going until it's done the 0 to 255 before it accepts anything else.

v2 code below, and a quick video demo of the breadboard prototype a couple of posts back.

Code: [Select]
#include <OneButton.h> // The OneButton Library
#include <jled.h> // The Jled Library

// Naming the pins
/* If you're using different pins then an Arduino Nano (I'm using a chinese DCCduino)
You only have to change pin numbers here, all further instances will go back here.*/

int effectbutton = 7;    // Footswitch to turn the effect on and off (and with 'shift function' active, switch to the Line6 preset screen.)
int tapbutton = 5;       // Footswitch tap tempo input
int effectled = 10;      // The output driving the LED/LDR
int tempoled = 6;        // The Led providing visual tempo feedback
int tempopot = A0;       // The connection for the manual tempo pot (10kB, middle lug to the pin, with ground and 5v either side)

/* I've included 3 inputs for waveform selection, which (if you have the right switching setup)
gives you 8 selectable presets. However, if using a 'shift function' (one extra input) this doubles to 16.
A future version is planned with some 'single shot' presets that might use such a setup.
Pins are high by default and pulled to ground to set.*/

int waveselect1 = 2;      // First input line for waveform selection
int waveselect2 = 3;      // Second input line for waveform selection
int waveselect3 = 4;      // Third input line for waveform selection

int line6effect = 8;      // This DOES NOT go straight to the Line6 footswitch (as it uses 3.3v logic) first to a CD4066 that pulls the Line6 footswitch LOW
int line6tap = 9;         // This DOES NOT go straight to the Line6 footswitch (as it uses 3.3v logic) first to a CD4066 that pulls the Line6 footswitch LOW

/* You can use the shift function for the above mentioned expanded presets (from 8 to 16)
if you're not using the footswitch to select presets on a Line6 M5 or M9. Will need the appropriate coding, which is currently in development*/
int shiftfunction = 13;   // Double tapping the effect footswitch engages the 'shift function' and lights the shift LED to show it is active

//Declaring the waveforms
/* This will vary on how you set up the waveform selection. Based on what type of waveform selection solution
you come up with you can theoretically have x amount of options as Jled is extremely flexible when it comes
to generating waveforms :-) check out: https://github.com/jandelgado/jled */

auto ledtempo = JLed(tempoled);      // This Led is always on and provides visual tempo feedback
auto ledon = JLed(effectled);        // Technically just 'always on' so not really a 'waveform'
auto ledsquare = JLed(effectled);    // 50% on 50% off
auto ledsine = JLed(effectled);      // Smooth cycle between 0% and 100%
auto ledrampup = JLed(effectled);    // Smooth cycle up to 100% for the duration of the tap, then start over
auto ledrampdown = JLed(effectled);  // Start at 100% with a smooth cycle down to 0%, then start over
auto ledrandom = JLed(effectled);    // Based on the Jled 'candle' setting, basically random values with user setting for 'jitter'

//Defining values for the tap tempo functions

#define THRESHOLD 30    // Threshold for the 'tempo pot' to overtake the tapped tempo. 20 is default, increase if too glitchy
#define MAXDELAY 3000   // Maximum delay time in Msec that can be achieved with the tempo pot
#define MINDELAY 10     // Maximum delay time in Msec that can be achieved with the tempo pot
#define MAXTAP 8000     // Maximum delay time in Msec that can be achieved with the tap tempo

//Declaring the 'Onebutton' footswitch
OneButton effect = OneButton(
  effectbutton,   // Input pin for the button
  true,           // Button is active LOW
  true            // Enable internal pull-up resistor
);

// Variables that will change when running:

int buttonPushCounter = 0;    // counter for the number of button presses
int doubleTapCounter = 0;     // counter for the number of doubletaps (heh, Zombieland! :-)
int lastTapState = LOW;       // the last tap button state
int period = 2000;            // Default start time for the tempo
int lastVal = 0;              // Initialise the tap tempo button
int tempAnalog = 0;           // Initialise the manual tempo pot
boolean PotControls = true;   // Allows the tempo pot to take over the tapped tempo
unsigned long currentTimer[2] = { 500, 500 };  // array of most recent tap counts
unsigned long lastTap = 0;    // when the last tap happened */

void setup()

  // Binding functions to what happens to the effectbutton
  effect.attachClick(effectsingleclick);                  // link the function to be called on a singleclick event.
  effect.attachDoubleClick(effectdoubleclick);            // link the function to be called on a doubleclick event.
  effect.attachLongPressStart(effectlongpressstart);      // link the function to be called at the start of a longpress event.
  effect.attachLongPressStop(effectlongpressstop);        // link the function to be called at the end of a longpress event.
  effect.attachDuringLongPress(effectlongpress);          // link the function to be called during a longpress event.

  // Set timings for the effectbutton, you can tweak these to get it working just how you like it
  effect.setClickTicks(200);   // Time for registering single click. 200 works well but if your double clicks aren't reliable increase it a bit (start with 100 increment)
  effect.setPressTicks(500);   // Time for registering Long Press. 500 (half a second) is good enough for me, but see if shorter still works for you)

  // Set pin modes for all the pins, and pull them high or low at the start if needed
  pinMode(waveselect1, INPUT_PULLUP );         // Used for waveform selection with a two pin, 6 position rotary dial
  pinMode(waveselect2, INPUT_PULLUP );         // Used for waveform selection with a two pin, 6 position rotary dial
  pinMode(waveselect3, INPUT_PULLUP );         // Used for waveform selection with a two pin, 6 position rotary dial
  pinMode(tapbutton, INPUT_PULLUP );           // tap button - press it to set the tempo
  pinMode(tempoled, OUTPUT);                   // This is the LED showing the current tempo
  digitalWrite(line6effect, LOW);              // Make sure this starts low to not trigger the Line6 effect on/off button
  pinMode(line6effect, OUTPUT);                // This goes to Line6 Effect On/Off button
  digitalWrite(line6tap, LOW);                 // Make sure this starts low to not trigger the Line6 tap button
  pinMode(line6tap, OUTPUT);                   // This goes to Line6 Tap button
  pinMode(effectled, OUTPUT);                  // This goes to the LED/LDR that controls the expression pedal
  digitalWrite(shiftfunction, LOW);            // Make sure this starts low at startup
  pinMode(shiftfunction, OUTPUT);              // LED that shows when doubleclick function is active
 
  // initialize serial communication (for debugging):
  Serial.begin(9600);
}

void loop()
{
  /* read the button on pin 5, and only pay attention to the
     HIGH-LOW transition so that we only register when the
     button is first pressed down */
  int tapState = digitalRead( tapbutton );
  if( tapState == LOW && tapState != lastTapState )
    {
      tap(); /* we got a HIGH-LOW transition, call our tap() function */
    }
  lastTapState = tapState; /* keep track of the state */
 
  ledtempo.Update();    // update leds
  delay(1);
 
  effect.tick();  // Check the effect button for click/doubleclick/longpress events
 
  poll_pot();           // check pot value

  /* If the effect is switched on in latching mode, toggle between continuously updating the waveform
  or incrementing the counter when switched off so it's ready for the next event*/
 
  if (buttonPushCounter %2 != 0) {
    update_effect(); }
  else {
      buttonPushCounter = 0;
    }
}

void tap()
{
  /* we keep two of these around to average together later */
  currentTimer[1] = currentTimer[0];
  currentTimer[0] = millis() - lastTap;
  lastTap = millis();
 
  period = ((currentTimer[0] + currentTimer[1])/2);   // establish new period

  PotControls = false;   // override the pot when tapping a tempo

  update_leds();   // update leds period
}

void update_leds(){   // Update the led's tempo information after tapping or setting the tempo pot
 
  ledtempo = JLed(tempoled).Blink(period/20, period/20*19).Forever(),
  ledon = JLed(effectled).On().Forever();
  ledsquare = JLed(effectled).Blink(period/2, period/2).Forever();
  ledsine = JLed(effectled).Breathe(period).DelayAfter(0).Forever();
  ledrampup = JLed(effectled).FadeOn(period).DelayBefore(0).Forever();
  ledrampdown = JLed(effectled).FadeOff(period).DelayBefore(0).Forever();
  ledrandom = JLed(effectled).Candle(period/286 /*speed*/, 255 /* jitter*/);
}

void update_effect(){   // Keep the led waveform updated in the loop until it's turned off
 
  int waveVal = 0;          // 3bit value that selects the right preset based on sum of the waveselect read below
  if (digitalRead(2) == HIGH) { waveVal += 4; }
  if (digitalRead(3) == HIGH) { waveVal += 2; }
  if (digitalRead(4) == HIGH) { waveVal += 1; }

  // Light the correct LED
  switch (waveVal) {
  case 2:
    ledsquare.Update();
    break;
  case 3:
    ledon.Update();
    break;
  case 4:
    ledsine.Update();
    break;
  case 5:
    ledrandom.Update();
    break;
  case 6:
    ledrampup.Update();
    break;
  case 7:
    ledrampdown.Update();
    break;
  } 
}

void poll_pot() {                   // Allow tempo pot reading to override tapped tempo

  tempAnalog = analogRead(tempopot);    // Read analog value
 
  // Convert 10 bit to milliseconds, 3 seconds maximum
  tempAnalog = map(tempAnalog, 0, 1023, 0, MAXDELAY);
  tempAnalog = constrain(tempAnalog, MINDELAY, MAXDELAY);
 
  // Check pot value and compare variation with threshold
  if((abs(tempAnalog-lastVal)>THRESHOLD))
  {
    // update period with pot value
    period = tempAnalog;
    //return control to Pot   
    PotControls = true;
    // Store current value to be used laters
    lastVal = tempAnalog;
    // update leds with new timing
    update_leds();
  }
}


// effect button actions

void effectsingleclick(){
   
  buttonPushCounter++;      // every time the effect turns on, increment the counter
  if (buttonPushCounter %2 != 0 && doubleTapCounter == 0 ) {  // Determines if the effect is currently off and is not in the double click function

   effectlongpressstart();  // Run through longpressstart in case 'ramp up' function is selected
   update_effect();         // Run the chosen waveform until the effect is turned off
  }
  /* If a doubleclick is active, make sure the led stays off and fire a momentary pulse
  (in my case to a CD4066 cmos switch) to switch the Line6 effect and tap buttons   
  simultenously to trigger the preset select.*/
  else if (doubleTapCounter != 0 ) { 
  ledon.Stop();
  ledrandom.Stop();
  ledsquare.Stop();
  ledsine.Stop();
  ledrampup.Stop();
  ledrampdown.Stop();
  digitalWrite(line6effect, !digitalRead(line6effect));         // This goes to Line6 Effect On/Off button
  digitalWrite(line6tap, !digitalRead(line6tap));        // This goes to Line6 Tap button
  delay(100);
  digitalWrite(line6effect, !digitalRead(line6effect));         // This goes to Line6 Effect On/Off button
  digitalWrite(line6tap, !digitalRead(line6tap));        // This goes to Line6 Tap button
  }
/* If there's no doubleclick active, another single click will go to longpresstop,
in case there is a 'ramp down' selected.*/
  else {
    effectlongpressstop();
  }
}

void effectdoubleclick(){
/* When a double click is detected increment the counter, otherwise reset the
counter to 0, effectively a toggle.*/
  if (doubleTapCounter %2 == 0) {
    doubleTapCounter++;
     Serial.print("double click on");
     Serial.println(doubleTapCounter);
  }
  else {
    doubleTapCounter = 0;
    Serial.print("reset the counter");
  }
  digitalWrite(shiftfunction, !digitalRead(shiftfunction));         // Toggle the indicator light for doubleclick on/off
}
   

void effectlongpressstart(){
  /* At the start of momentary or latching (they both cycle through longpresstart)
  Reset the LED so the timing starts from the moment you turn it on, and ramp up
  to the waveform if 'ramp up' is selected.*/
  ledon.Reset();
  ledrandom.Reset();
  ledsquare.Reset();
  ledsine.Reset();
  ledrampup.Reset();
  ledrampdown.Reset();
  // what happens when momentary effect engagement starts (ramp up)
 if (digitalRead(waveselect1) == LOW && digitalRead(waveselect2) == HIGH && digitalRead(waveselect3) == HIGH) { // if Always on LED is selected
  //Fading in the LED
  for(int i=0; i<255; i++){
    analogWrite(10, i);
    delay(period/64); //Increasing this value will give a longer ramp up time, can set to time in msec (10 default) or 'period/xxx' for tap related length
  }
}
}

void effectlongpress(){

  int waveVal = 0;          // 3bit value that selects the right preset based on sum of the waveselect read below
  if (digitalRead(2) == HIGH) { waveVal += 4; }
  if (digitalRead(3) == HIGH) { waveVal += 2; }
  if (digitalRead(4) == HIGH) { waveVal += 1; }

  // Light the correct LED
  switch (waveVal) {
  case 2:
    ledsquare.Update();
    break;
  case 3:
    ledon.Update();
    break;
  case 4:
    ledsine.Update();
    break;
  case 5:
    ledrandom.Update();
    break;
  case 6:
    ledrampup.Update();
    break;
  case 7:
    ledrampdown.Update();
    break;
  } 

}

void effectlongpressstop(){
  /* At the end of momentary or latching (they both cycle through longpresstop)
  Increment the counter, and ramp down if 'ramp down' is selected.
  Then turn off the LED completely with a digitalwrite as sometimes it kept glowing. :-) */
  buttonPushCounter = 0;

  // what happens when momentary effect engagement ends (ramp down)
  if (digitalRead(waveselect1) == LOW && digitalRead(waveselect2) == HIGH && digitalRead(waveselect3) == HIGH) { // Always On LED is selected
  for(int i=255; i>0; i--){
    analogWrite(10, i);
    delay(10);  //Increasing this value will give a longer ramp up time, can set to time in msec (10 default) or 'period/xxx' for tap related length
    }
  }
digitalWrite(effectled,LOW);  // Switch off the LED
}
Title: Re: Arduino tap tempo LED/LDR output w. 6 waveform presets, ramp up/down, etc...
Post by: niektb on May 27, 2021, 07:48:08 AM
You could abbreviate the triple if-statement to
Code: [Select]
int waveVal = digitalRead(4) << 2 | digitalRead(3) << 1 | digitalRead(2);Also, you're not very consistent with where you put your brackets, it will improve readability if you do :)
But these are details, looks pretty cool overal!
Title: Re: Arduino tap tempo LED/LDR output w. 6 waveform presets, ramp up/down, etc...
Post by: ElectricDruid on May 27, 2021, 08:14:47 AM
One other small nit-pick:

The pin set-up definitions should be defined as constants, like this:

Code: [Select]
const int tapbutton = 5;       // Footswitch tap tempo input

The reason for doing this is that if the compiler knows that these are constants, it will give you a useful error message if tapbutton gets set to some other value by accident later on in the code.

+1 what Niektb said. These are details and it's looking good now.
Title: Re: Arduino tap tempo LED/LDR output w. 6 waveform presets, ramp up/down, etc...
Post by: niektb on May 27, 2021, 08:44:58 AM
Do the Line6 M-series convert the exp input to discrete values? Does anybody know that?
So could it for example be possible to achieve the same functionality using MIDI?
Title: Re: Arduino tap tempo LED/LDR output w. 6 waveform presets, ramp up/down, etc...
Post by: any on May 27, 2021, 08:58:10 AM
Do the Line6 M-series convert the exp input to discrete values? Does anybody know that?
So could it for example be possible to achieve the same functionality using MIDI?

Actually, just checked and yes, they accept midi CC 0-127 for expression input.
Would that convert easily from pwm or is it a whole other approach?
Title: Re: Arduino tap tempo LED/LDR output w. 6 waveform presets, ramp up/down, etc...
Post by: niektb on May 27, 2021, 01:46:03 PM
Do the Line6 M-series convert the exp input to discrete values? Does anybody know that?
So could it for example be possible to achieve the same functionality using MIDI?

Actually, just checked and yes, they accept midi CC 0-127 for expression input.
Would that convert easily from pwm or is it a whole other approach?

No MIDI is quite different from that, you need to use the uart on your Arduino :)
Title: Re: Arduino tap tempo LED/LDR output w. 6 waveform presets, ramp up/down, etc...
Post by: any on May 27, 2021, 07:41:09 PM
Do the Line6 M-series convert the exp input to discrete values? Does anybody know that?
So could it for example be possible to achieve the same functionality using MIDI?

Actually, just checked and yes, they accept midi CC 0-127 for expression input.
Would that convert easily from pwm or is it a whole other approach?


No MIDI is quite different from that, you need to use the uart on your Arduino :)

Ok, so for the sake of fun&games I've done the following:
- re-arranged the pins to free up d2 which is an interrupt pin
- attached an interrupt
Code: [Select]
  Serial.begin(115200);
  // when pin D2 goes high, call the rising function
  attachInterrupt(0, rising, RISING);
- the added the following functions
Code: [Select]
void rising() {
  attachInterrupt(0, falling, FALLING);
  prev_time = micros();
}
 
void falling() {
  attachInterrupt(0, rising, RISING);
  pwm_value = micros()-prev_time;
  pwm_value = map(pwm_value, 8, 1828, 0, 127);
  Serial.println(pwm_value);
}
- Connected LED output to the LDR to pin 2

Looking at the serial monitor I now get 0-127 values when the effect is engaged (aside from 'always on' and 'squarewave')
This gives me a mapped value 0 to 127 that I can then send over midi no? (not implemented yet, but looks easy enough)
So that way you can have either midi or analog 'expression control modulation'

Only thing i notice at the moment is it doesn't read continous on the square wave as it's only on or off so no good for the interrupt?
(or is there a way to make that work)

Also, is this ok performance wise? or is it inefficient to have a routine like that running?

Mind you, I'm new to this, so just trying things out... Ha!
Title: Re: Arduino tap tempo LED/LDR output w. 6 waveform presets, ramp up/down, etc...
Post by: any on May 29, 2021, 08:04:32 AM
Ok, apart from the above folley (would be nice to have both midi and analog options available, and would definitely expand compatibility ) I’m having a hard time trying to ‘abort’ the ramp up function in ‘longpressstart’. That is, i can get a state change detection working that switches successfully to ‘longpressstop’ (So during the ramp up, i can press the footswitch again and it will immediately ramp down from 255 to 0, so that works) but it then returns to the function instead of the loop and the led stays on no matter what i’ve tried (if, if else, break, return, etc) might just be looking at it the wrong way?

What i’m trying to achieve is a ‘ramp up’ that - when aborted (a state change during the ramping up) will ramp down again from the value where it was aborted. So assume i need to keep the current value as a ‘int rampbrightness = X;’ or something so when longpressstop takes over it will not start at 255 but rather the stored value. Any ideas how to make that work? I’ll keep digging but any input appreciated. ;-) already learned a lot over the past week. Cheers
Title: Re: Arduino tap tempo LED/LDR output w. 6 waveform presets, ramp up/down, etc...
Post by: any on May 31, 2021, 12:04:18 AM
Ok, apart from the above folley (would be nice to have both midi and analog options available, and would definitely expand compatibility ) I’m having a hard time trying to ‘abort’ the ramp up function in ‘longpressstart’. That is, i can get a state change detection working that switches successfully to ‘longpressstop’ (So during the ramp up, i can press the footswitch again and it will immediately ramp down from 255 to 0, so that works) but it then returns to the function instead of the loop and the led stays on no matter what i’ve tried (if, if else, break, return, etc) might just be looking at it the wrong way?

What i’m trying to achieve is a ‘ramp up’ that - when aborted (a state change during the ramping up) will ramp down again from the value where it was aborted. So assume i need to keep the current value as a ‘int rampbrightness = X;’ or something so when longpressstop takes over it will not start at 255 but rather the stored value. Any ideas how to make that work? I’ll keep digging but any input appreciated. ;-) already learned a lot over the past week. Cheers

Ok, Think I cracked that one... the ramp-up can be 'aborted' at any time (both in momentary and latching) and it will fade the LED back to off and reset (back to loop) from whatever value the ramp-up function left it. Ideally I would have it endlessly back-and-forth but for now it will not allow the 'abort' function to be interrupted until it is back to the main loop. Still, interesting possibilities as you can 'pump' the effect manually now for short swells.

Code: [Select]
void effectsingleclick()
{   
  buttonPushCounter++;      // every time the effect turns on, increment the counter
  if (buttonPushCounter %2 != 0 && doubleTapCounter == 0 )
  {  // Determines if the effect is currently off and is not in the double click function
   effectlongpressstart();  // Run through longpressstart in case 'ramp up' function is selected
   if (cancelrampup == 0){
   update_effect();         // Run the chosen waveform until the effect is turned off
   }
   if (cancelrampup == 1){
    ledon.Stop();
    ledrandom.Stop();
    ledsquare.Stop();
    ledsine.Stop();
    ledrampup.Stop();
    ledrampdown.Stop();
    cancelrampup = 0;
    buttonPushCounter = 0;
   }
  }
  /* If a doubleclick is active, make sure the led stays off and fire a momentary pulse
  (in my case to a CD4066 cmos switch) to switch the Line6 effect and tap buttons   
  simultenously to trigger the preset select.*/
  else if (doubleTapCounter != 0 )
  { 
  ledon.Stop();
  ledrandom.Stop();
  ledsquare.Stop();
  ledsine.Stop();
  ledrampup.Stop();
  ledrampdown.Stop();
  digitalWrite(line6effect, !digitalRead(line6effect));         // This goes to Line6 Effect On/Off button
  digitalWrite(line6tap, !digitalRead(line6tap));               // This goes to Line6 Tap button
  delay(100);
  digitalWrite(line6effect, !digitalRead(line6effect));         // This goes to Line6 Effect On/Off button
  digitalWrite(line6tap, !digitalRead(line6tap));               // This goes to Line6 Tap button
  }
/* If there's no doubleclick active, another single click will go to longpresstop,
in case there is a 'ramp down' selected.*/
  else
  {
    effectlongpressstop();
  }
}

void effectlongpressstart()
{
  /* At the start of momentary or latching (they both cycle through longpresstart)
  Reset the LED so the timing starts from the moment you turn it on, and ramp up
  to the waveform if 'ramp up' is selected.*/
  ledon.Reset();
  ledrandom.Reset();
  ledsquare.Reset();
  ledsine.Reset();
  ledrampup.Reset();
  ledrampdown.Reset();
  int rampvalue = 0;
  int buttonState = digitalRead(effectbutton);
  int lastButtonState = digitalRead(effectbutton);
  // what happens when momentary effect engagement starts (ramp up)
  int waveVal = digitalRead(waveselect1) << 2 | digitalRead(waveselect2) << 1 | digitalRead(waveselect3); // 3bit value that selects the right preset based on sum of the waveselect read below
  // Light the correct LED
  switch (waveVal)
  {
    case 3:
     //Fading in the LED
    for(rampvalue; rampvalue<255; rampvalue++)
    {
      buttonState = digitalRead(effectbutton);
      analogWrite(effectled, rampvalue);
      delay(period/64); //Increasing this value will give a longer ramp up time, can set to time in msec (10 default) or 'period/xxx' for tap related length
      lastButtonState = digitalRead(effectbutton);
      if(buttonState != lastButtonState)
        {
          for(rampvalue; rampvalue>0; rampvalue--)
          {
          analogWrite(effectled, rampvalue);
          delay(period/64); //Increasing this value will give a longer ramp up time, can set to time in msec (10 default) or 'period/xxx' for tap related length
          }
         cancelrampup = 1;
         buttonPushCounter = 2;
         return;
        }
    }
    break;
  }
}

void effectlongpress()
{
  int waveVal = digitalRead(waveselect1) << 2 | digitalRead(waveselect2) << 1 | digitalRead(waveselect3); // 3bit value that selects the right preset based on sum of the waveselect read below
  // Light the correct LED
  switch (waveVal)
  {
  case 2:
    ledsquare.Update();
    break;
  case 3:
    ledon.Update();
    break;
  case 4:
    ledsine.Update();
    break;
  case 5:
    ledrandom.Update();
    break;
  case 6:
    ledrampup.Update();
    break;
  case 7:
    ledrampdown.Update();
    break;
  } 
}

void effectlongpressstop()
{
  /* At the end of momentary or latching (they both cycle through longpresstop)
  Increment the counter, and ramp down if 'ramp down' is selected.
  Then turn off the LED completely with a digitalwrite as sometimes it kept glowing. :-) */

  // what happens when momentary effect engagement ends (ramp down)
  int waveVal = digitalRead(waveselect1) << 2 | digitalRead(waveselect2) << 1 | digitalRead(waveselect3); // 3bit value that selects the right preset based on sum of the waveselect read below
  Serial.println(cancelrampup);
  if (cancelrampup == 1)
  {
    int rampvalue = 0;
    cancelrampup = 0;
    buttonPushCounter = 0;
    return;
  }
  // Light the correct LED
  switch (waveVal)
  {
    case 3:
     //Fading in the LED
    for(int rampvalue=255; rampvalue>0; rampvalue--)
          {
          analogWrite(effectled, rampvalue);
          delay(period/64); //Increasing this value will give a longer ramp up time, can set to time in msec (10 default) or 'period/xxx' for tap related length
          }
    digitalWrite(effectled,LOW);  // Switch off the LED
    buttonPushCounter = 0;
    break;
  }
}
Title: Re: Arduino tap tempo LED/LDR output w. 6 waveform presets, ramp up/down, etc...
Post by: any on June 01, 2021, 10:37:28 PM
Ok, managed to get this working  ;D

A momentary OR latching mode that will 'pump' back and forth between ramp-up and ramp-down if there is a state change. Indefinitely and from the value where the state change occurs.

So the scenario for latching is is:
1. Footswitch press on, LED starts to ramp up to full brightness (based on tap tempo, currently period/16, which is quite slow as it will take 255 x period/16 to reach full brightness)
2. Footswitch pressed again during ramp up period, say half way = abort ramp up, LED will now start to ramp back down from the moment the switch was pressed.
  (so say the pwm value got to 130 when the switch was pressed, it will start to decrease from there)
3. Footswitch pressed again during ramp-down period = re-engage ramp up, and the LED will start to increase brightness.
4. Press again, see 2. etc...

- If it reaches full brightness (completes the ramp up) it switches to the next function
(either 'updateeffect' or 'effectlongpress' depending on latching or momentary engagement of the first instance).
- If it reaches '0' on the 'abort' ramp down and it will reset and go back to loop.

Just had a little play and I think it's really useful, especially with very slow ramp times as it's a bit like having an
actual foot expression pedal connected for slow swells. I will need to start thinking about ways to select ramp times,
either based on tapped tempo or in msec, bars etc. (probably LCD with rotary encoder so there's a larger selection of
waveforms to choose, midi functions?) This could get quite funky!

Code below (just for all the button events, which uses the onebutton library)
Cleaned up and annotated so it's (hopefully) clear what's going on:
Feedback welcome, wouldn't be surprised if there is some streamlining possible.

Code: [Select]
void effectsingleclick()
{   
  buttonPushCounter++;      // every time the effect turns on, increment the counter
  Serial.println(buttonPushCounter);
  if (buttonPushCounter %2 != 0 && doubleTapCounter == 0 )
  {  // Determines if the effect is currently off and is not in the double click function
   effectlongpressstart();  // Run through longpressstart in case 'ramp up' function is selected
   if (cancelrampup == 0){
   update_effect();         // Run the chosen waveform until the effect is turned off
   }
   if (cancelrampup == 1){  // If 'effectlongpressstart' was canceled and 'ramped down' to zero, this will reset everything for the next effectbutton event
    ledon.Stop();
    ledrandom.Stop();
    ledsquare.Stop();
    ledsine.Stop();
    ledrampup.Stop();
    ledrampdown.Stop();
    cancelrampup = 0;
    buttonPushCounter = 0;
   }
  }
  /* If a doubleclick is active, make sure the led stays off and fire a momentary pulse
  (in my case to a CD4066 cmos switch) to switch the Line6 effect and tap buttons   
  simultenously to trigger the preset select.*/
  else if (doubleTapCounter != 0 )
  { 
  ledon.Stop();
  ledrandom.Stop();
  ledsquare.Stop();
  ledsine.Stop();
  ledrampup.Stop();
  ledrampdown.Stop();
  digitalWrite(line6effect, !digitalRead(line6effect));         // Pulls Line6 Effect On/Off button low (to ground) via CD4066 switch
  digitalWrite(line6tap, !digitalRead(line6tap));               // Pulls Line6 Tap button low (to ground) via CD4066 switch
  delay(100);                                                   // little pause to simulate a momentary footswitch press/depress
  digitalWrite(line6effect, !digitalRead(line6effect));         // Pulls Line6 Effect On/Off button low (to ground) via CD4066 switch
  digitalWrite(line6tap, !digitalRead(line6tap));               // Pulls Line6 Tap button low (to ground) via CD4066 switch
  }
/* If there's no doubleclick active, another single click will go to longpresstop,
in case there is a 'ramp down' selected.*/
  else
  {
    effectlongpressstop();
  }
}

void effectlongpressstart()
{
  /* At the start of momentary or latching (they both cycle through longpresstart)
  Reset the LED so the timing starts from the moment you turn it on, and ramp up
  to the waveform if 'ramp up' is selected.*/
  ledon.Reset();
  ledrandom.Reset();
  ledsquare.Reset();
  ledsine.Reset();
  ledrampup.Reset();
  ledrampdown.Reset();
  int rampvalue = 0;                                // This value stores the current brightness of the LED during ramp up/down, always starts at 0 (off)
  int buttonState = digitalRead(effectbutton);      // This value stores the current state of the button for state change detection
  int lastButtonState = digitalRead(effectbutton);  // This value stores the previous state of the button for state change detection
  bool rampback = false;                            // rampback is used to toggle between ramp up / ramp down as long as either is not finished.
 
  // 3bit value that selects the right switch case based on sum of the waveselect pins read below
  int waveVal = digitalRead(waveselect1) << 2 | digitalRead(waveselect2) << 1 | digitalRead(waveselect3);
  /*Currently there is only one switch case (3), that corresponds to the 'always on' waveform setting.
  As all the other wave forms either start at 0, are random, or do contunous ramp up or ramp down.
  So it didn't seem as useful there, but might add functionalities to this in future versions*/
  switch (waveVal)
    {
      case 3:
      // Fading in the LED
      // This while loop makes sure the only ways to exit the loop are a completed 'abort' back to off, or a succesful 'ramp up' to full brightness
      while ( rampback == true && rampvalue != 0 || rampback == false && rampvalue != 255 )
         {
           // This while loop makes sure we chose to 'ramp up' the LED and it's not already at full brightness (not sure if it needs the brightness statement)
           while ( rampback == false && rampvalue != 255)
            {
            // Reset the button states (default state is high, state change on low)
            buttonState = 1;
            lastButtonState = 1;
            // A bit of delay to make sure there is no bounce interference from the button
            delay(300);
            // Until rampvalue reaches 255 (full brightness) keep increasing brightness
            for(rampvalue; rampvalue<255; rampvalue++)
              {
                buttonState = digitalRead(effectbutton);        // Read the effect button to see if it has changed state
                analogWrite(effectled, rampvalue);              // Rampvalue keeps increasing until full brightness (255)
                delay(period/16);                               // Increasing this value will give a longer ramp up time, can set to time in msec (10 default) or 'period/xxx' for tap related length
                lastButtonState = digitalRead(effectbutton);    // Read the effect button to see if it has changed state
                // If during ramp up the switch changes state, we initiate an 'abort function'
                if(buttonState != lastButtonState)
                  {
                   rampback = true;                             // Set rampback to true will change the while loop from 'ramp up' to 'ramp down'
                   break;                                       // break the while loop to initiate based on the new boolean statement (rampback = true)
                  }
              }
          }
      // This while loop makes sure we chose to 'ramp down' the LED and it's not already off (not sure if it needs the brightness statement)
      while ( rampback == true && rampvalue != 0 )
          {
          // Reset the button states (default state is high, state change on low)
          buttonState = 1;
          lastButtonState = 1;
          // A bit of delay to make sure there is no bounce interference from the button
          delay(300);
          // Until rampvalue reaches 0 (off) keep decreasing brightness
          for(rampvalue; rampvalue>0; rampvalue--)
            {
                buttonState = digitalRead(effectbutton);        // Read the effect button to see if it has changed state
                analogWrite(effectled, rampvalue);              // Rampvalue keeps increasing until full brightness (255)
                delay(period/16);                               // Increasing this value will give a longer ramp up time, can set to time in msec (10 default) or 'period/xxx' for tap related length
                lastButtonState = digitalRead(effectbutton);    // Read the effect button to see if it has changed state
                // If during ramp up the switch changes state, we switch back to the 'ramp up function'
                if(buttonState != lastButtonState)
                {
                  rampback = false;                             // Set rampback to true will change the while loop from 'ramp down' to 'ramp up'
                  break;                                        // break the while loop to initiate based on the new boolean statement (rampback = false)
                }
            }
           // If the ramp up is canceled in full (aborted during ramp up and ramped down to '0') we need to do a couple of things to reset the system
           if (rampvalue == 0)
            {
            cancelrampup = 1;                                   // Set a boolean so the 'single click' event is reset when it returns from here.
//            buttonPushCounter = 2;                            // I think we don't need this as it's set to '0' when we do the 'return'
            digitalWrite(effectled,LOW);                        // Switch off the LED
            return;                                             // Go back to the function that called it, in this case effectsingleclick
            }
          }
        }
       break;
    }
}

void effectlongpress()
// Effectlongpress is a momentary action, so as long as the footswitch is pressed it will loop and update the led
{
  // 3bit value that selects the right switch case based on sum of the waveselect pins read below
  int waveVal = digitalRead(waveselect1) << 2 | digitalRead(waveselect2) << 1 | digitalRead(waveselect3);
  // Light the correct LED
  switch (waveVal)
  {
  case 2:
    ledsquare.Update();
    break;
  case 3:
    ledon.Update();
    break;
  case 4:
    ledsine.Update();
    break;
  case 5:
    ledrandom.Update();
    break;
  case 6:
    ledrampup.Update();
    break;
  case 7:
    ledrampdown.Update();
    break;
  } 
}

void effectlongpressstop()  // what happens when momentary effect engagement ends (ramp down)
{
  /* At the end of momentary or latching (they both cycle through longpresstop)
  Increment the counter, and ramp down if 'ramp down' is selected.
  Then turn off the LED completely with a digitalwrite as sometimes it kept glowing. :-) */
  // 3bit value that selects the right preset based on sum of the waveselect read below
  int waveVal = digitalRead(waveselect1) << 2 | digitalRead(waveselect2) << 1 | digitalRead(waveselect3);
//  if (cancelrampup == 1)
//  {
//    int rampvalue = 0;
//    cancelrampup = 0;
//    buttonPushCounter = 0;
//    return;
//  }
  switch (waveVal)
  {
    case 3:
     //Fading out the LED
    for(int rampvalue=255; rampvalue>0; rampvalue--)
          {
          analogWrite(effectled, rampvalue);
          delay(period/64); //Increasing this value will give a longer ramp up time, can set to time in msec (10 default) or 'period/xxx' for tap related length
          }
    digitalWrite(effectled,LOW);  // Switch off the LED
    buttonPushCounter = 0;        // Reset the counter
    break;
  }
}

void effectdoubleclick()
{
/* When a double click is detected increment the counter, otherwise reset the counter to 0,
 effectively a toggle. Double click initiates a 'shift function' where the latching
 (single click) performs a different function, the momentary functions don't change */
  if (doubleTapCounter %2 == 0)
  {
    doubleTapCounter++;         // Turn on the 'shift function'
  }
  else
  {
    doubleTapCounter = 0;       // Reset the 'shift function' (off)
  }
  digitalWrite(shiftfunction, !digitalRead(shiftfunction));         // Toggle the indicator light for doubleclick on/off
}
Title: Re: Arduino tap tempo LED/LDR output w. 6 waveform presets, ramp up/down, etc...
Post by: any on June 07, 2021, 06:42:17 AM
Heh, just talking to myself at the moment... is it ok to post follow ups to myself?
Anyone still keeping tabs on this? anyway... Here's where it's at.

I have the Line6 M5 v1 pretty much done based on an Arduino Pro Mini. All function seem to work well, and I'm currently cramming this onto a 15x15 through hole project board.
I might need to ditch socketing the Pro mini for height restrictions inside the M5 (will post pictures of the finished thing, it'll be tight)

I'll quickly list controls for the M5:
- Effect footswitch (on/off momentary or latching, or after double tap, only on/off latching for the modulation and a single tap brings up the presets on the M5)
- Tempo Pot
- Expression Pot (for manual control, and for editing patches at min/max values when modulation is off. also works as intensity control as pot at 0 resistance gives no modulation)
- Waveform selection (mini rotary dial with 6 positions) always on (with ramp function), square, sine, ramp up, ramp down, random
- 3 way mini toggle for 'multiplier' select, either 1x, 2x, or 4x the tapped duration (these values might still change after testing)
- 3 way mini toggle for 'ramp' select, either off, fast(no cancel function), long
  ('long' can be manually 'cancelled' at any time during ramp up or down which will toggle direction of the ramp indefinitely until it's either fully up or down)

The only question I have at this stage, and I wonder if anyone can confirm/feed back/advice on the following is:
The Line6 M5 already has a 'tap' button, so I want to use the Line6 Tap button to pull both inputs LOW and set the same tempo that way on both circuits.
The Line6 is 3.3v logic though, and the Pro Mini is a 5v version.

Is there a way I can easily and safely pull both low with the same button? I was wondering if a diode or similar would allow the 5v on the footswitch without feeding into the 3.3v
going to the Line6. Otherwise I still have a spare switch on the 4066 so might utilise that to decouple the voltages and still pull them to ground simultaneously, but since it requires
switching logic which might (?) introduce timing issues, it would be ideal if both can be pulled low at the same time in a more direct way.

Current (in progress) layout of the board attached as well, for the curious folk ;-)

(https://i.postimg.cc/HrTcTvgw/Screen-Shot-2021-06-07-at-10-15-57-PM.png) (https://postimg.cc/HrTcTvgw)
Title: Re: Arduino tap tempo LED/LDR output w. 6 waveform presets, ramp up/down, etc...
Post by: ElectricDruid on June 07, 2021, 12:23:20 PM
Feedback welcome, wouldn't be surprised if there is some streamlining possible.

I'm still following along! It's fascinating to see someone else working through stuff that you've done yourself. Different people get to similar places by quite different routes most often, which keeps things interesting!

I've got a few comments, but they're mostly just about code style. You'll get away with doing it however you like for so long, but it ultimately pays off to develop a good style because it means you have less things to think about, and that helps when debugging and saves you time.

Code: [Select]
   if (cancelrampup == 0){
   update_effect();         // Run the chosen waveform until the effect is turned off
   }
   if (cancelrampup == 1){  // If 'effectlongpressstart' was canceled and 'ramped down' to zero, this will reset everything for the next effectbutton event
This bit should have an "elseif", since they can't happen together.

Your if statements and use of brackets is very variable. Sometimes you do:
Code: [Select]
if (condition) {
 // Stuff in here
}

Other times you do:
Code: [Select]
if (condition)
{
 // Stuff in here
}

It doesn't really matter which you do, but BE CONSISTENT! If you're trying to match up brackets in nested if statements or loops, it's a nightmare unless you're strict about stuff like this. I also set my tabs to 4 spaces so I get a nice big visible indent for each section and sub-section. That makes it visually easier to see what's connected to where.

Here's a messy example:
Code: [Select]
if (condiion)
{
  // Now we do some stuff
  // Ok, so do it.
  if (subcondition) {
    // Do some other stuff
  }
}

And here's a clearer example:
Code: [Select]
if (condiion) {
    // Now we do some stuff
    // Ok, so do it.
    if (subcondition) {
        // Do some other stuff
    }
}

I think you're in danger of getting lost in your clauses and sub-clauses. As an example, this statement does nothing:
Code: [Select]
  while ( rampback == true && rampvalue != 0 || rampback == false && rampvalue != 255 )

because it is followed by two statements that say:
Code: [Select]
while ( rampback == false && rampvalue != 255)
and
Code: [Select]
while ( rampback == true && rampvalue != 0 )
Clearly neither of those subsections could happen unless those initial conditions are true, so testing them on the way in is redundant - you're adding an extra layer of indenting to no purpose.

The logic is getting quite tangled here with all the options and sub-options you have. Well done for making it work, but I'd wonder if it's not time to sit down with a cool drink and a piece of blank paper and see if you can't find a neater way of capturing the required behaviour.

I think you've really gone far very quickly, so you have my respect and congratulations, and my comments are intended to help you get further whilst avoiding writing messy code that will lead to you getting bogged down in tedious debugging. That takes the fun out of it, and we don't want that!

Good luck!
Title: Re: Arduino tap tempo LED/LDR output w. 6 waveform presets, ramp up/down, etc...
Post by: any on June 07, 2021, 06:37:53 PM
I'm still following along! It's fascinating to see someone else working through stuff that you've done yourself. Different people get to similar places by quite different routes most often, which keeps things interesting!

Ha, good I was envisaging myself as some recluse in a cave working on something that only made sense in my own little world... hahaha!

Quote
I've got a few comments, but they're mostly just about code style. You'll get away with doing it however you like for so long, but it ultimately pays off to develop a good style because it means you have less things to think about, and that helps when debugging and saves you time.

Duly noted, I've got a session planned for that. Want to release a nice clean understandable sketch that everyone can read/adapt. ;-)

Quote
I think you're in danger of getting lost in your clauses and sub-clauses. As an example, this statement does nothing:
Code: [Select]
  while ( rampback == true && rampvalue != 0 || rampback == false && rampvalue != 255 )

because it is followed by two statements that say:
Code: [Select]
while ( rampback == false && rampvalue != 255)
and
Code: [Select]
while ( rampback == true && rampvalue != 0 )
Clearly neither of those subsections could happen unless those initial conditions are true, so testing them on the way in is redundant - you're adding an extra layer of indenting to no purpose.

The logic is getting quite tangled here with all the options and sub-options you have. Well done for making it work, but I'd wonder if it's not time to sit down with a cool drink and a piece of blank paper and see if you can't find a neater way of capturing the required behaviour.

Yeah, I know that all looks a bit convoluted  :icon_lol:
The logic behind it though (at least to me ) makes sense. I want the longer ramp time to have this 'infinite' flip/flop type switch
until it is either '0' (off) or '255' (on) to make them 'exit' the ramp function to the appropriate next step.
So with the two specific 'while' loops it will keep ramping either up or down continuously until it reaches that 'end' condition.

I did find a bug yesterday that had me add another condition (lol), but this is due to how I've utilised the 'onebutton' library.
After cancelling a 'ramp down' in latching mode, it goes back to the 'single click' function , which in turn would start turning on the LED again.

So, yes I agree the logic is getting a bit tangled  ;D and Sitting down with a cold drink and looking at this with fresh eyes might
present a more streamlined solution, ha!

Quote
I think you've really gone far very quickly, so you have my respect and congratulations, and my comments are intended to help you get further whilst avoiding writing messy code that will lead to you getting bogged down in tedious debugging. That takes the fun out of it, and we don't want that!

Good luck!

Thanks, no worries there, I completely see it as words of encouragement (My dayjob deals with lots of critique, and it is very useful). I knew I was getting into trouble when I started dreaming up this latched/momentary up/down ramping... I'll be keen to do a demo video of it though, as I think it's a very 'musical' and useful way to engage an effect. I'm going to use the same logic on my rehoused Digitech Multiplay (which is working great now btw!) But that'll need more work to interface. (although feedback would be easy enough as I already had a 'slam to 10' footswitch on it previously)
Title: Re: Arduino tap tempo LED/LDR output w. 6 waveform presets, ramp up/down, etc...
Post by: niektb on June 09, 2021, 03:16:50 AM
I'm still following along as well! I haven't been able to start coding my MIDI Controller (specifically designed for the M5 as well  :icon_mrgreen: ) Although I'm probably going down an entirely different route as well  :icon_mrgreen:
(as I have 3 buttons and I also want preset selection)
Title: Re: Arduino tap tempo LED/LDR output w. 6 waveform presets, ramp up/down, etc...
Post by: any on June 09, 2021, 07:49:12 PM
I'm still following along as well! I haven't been able to start coding my MIDI Controller (specifically designed for the M5 as well  :icon_mrgreen: ) Although I'm probably going down an entirely different route as well  :icon_mrgreen:
(as I have 3 buttons and I also want preset selection)

I found using the Onebutton library really easy to implement, and the 'double tap' on the effect button switches between 'preset select' and 'latching mode for the modulation'
I'm planning another version for the M9 as an external unit, which will have midi, and more user control with a 16x2 display and rotary encoder.
But first let's get this one done and dusted!

Picture from the workbench, practically finished the prototype pcb and decided on a ribbon cable interconnect as all controls will be fixed to the housing and the board will live just above the DSP.
Still wondering if I need some kind of shielding from the DSP and if it will get hot, but it looks like the best place and it would still allow removing the main pcb for any future servicing.

(https://i.postimg.cc/3W3T2R2C/Screen-Shot-2021-06-10-at-11-48-08-AM.png) (https://postimg.cc/3W3T2R2C)
Title: Re: Arduino tap tempo LED/LDR output w. 6 waveform presets, ramp up/down, etc...
Post by: potul on June 10, 2021, 04:42:18 AM
I also use OneButton. If you are interested I have a modified version of OneButton that is able to handle two button pushes at the same time triggering different actions.
Title: Re: Arduino tap tempo LED/LDR output w. 6 waveform presets, ramp up/down, etc...
Post by: any on June 10, 2021, 08:51:39 PM
I also use OneButton. If you are interested I have a modified version of OneButton that is able to handle two button pushes at the same time triggering different actions.

Sure, can come in handy some time. Although I was actually trying to get rid of that very functionality on the M5...  :icon_lol:

Cheers