tttapa / Control-Surface

Arduino library for creating MIDI controllers and other MIDI devices.
GNU General Public License v3.0
1.26k stars 140 forks source link

Various feature requests (visuals and shift register digital input) #335

Closed oscarosky closed 3 years ago

oscarosky commented 3 years ago

Hi Pieter, Some time ago I used your MIDI_controller library in a project and I became interested in Control Surface and the possibility of including motorized faders (I put that aside for a while). I have recently reviewed it again and I have seen a lot of progress.

Really great!

Your current Control Surface library allows many different combinations to customize a controller and I am very grateful to you for sharing it.

In order to expand these possibilities and also simplify the design of my circuit, I would be very happy if I could:

    • Send the VU value to a PWM pin (a possible VULEDPWM class?)
    • Send the VU value through FastLED (a possible VUFastLED class?)
    • Send the VPotRing value through FastLED (a possible VPotRingFastLED class?)
    • Use as input shift register SN74HC165 instead of CD74HC4051 (for buttons)

Excuse me if these things are already possible with Control Surface but I have not known how to do it.

The first question (VULEDPWM) would use it to illuminate led bars using LM3914 or LM3915.

The second (VUFastLED) would allow you to fully customize the vumet bridge just like DAWs.

The third (VPotRingFastLED) would greatly simplify the design of a `Rotary Encoder LED Ring´ and things would be possible such as using color codes depending on the parameter assigned to it.

Cheers

tttapa commented 3 years ago

The new-input feature branch has improved MIDI Input Elements, so you could do most of that manually without too much hassle. Basically, you just instantiate an MCU::VU object, and then you can the getDirty() and clearDirty() methods to inspect and clear the “dirty” flag, which allows you to know when the value changed. Then you can use the getValue() method to get the VU meter value and use that to write to a PWM pin.

MCU::VU vu(1); // VU meter for the first track
const uint8_t pwmpin = 13;

void setup() {
  Control_Surface.begin();
}

void loop() {
  Control_Surface.loop();
  if (vu.getDirty()) {
    analogWrite(pwmpin, 255 * vu.getValue() / 12);
    vu.clearDirty();
  }
}

Alternatively, you can implement your own class for PWM, have a look at the VULEDs class for inspiration. I think you only have to change the begin() and updateDisplay() methods, and remove the VULEDsDriver class from the inheritance list.

Personally, I think that driving an LM3915 using this method is a bad idea. You already have the digital values of the VU meter, so converting it to a PWM signal, then adding a low-pass filter to get an analog voltage, and then converting it to a digital LED meter again using the LM3915 is just unnecessarily complex, you'll have to make sure that the levels match, etc. I'd just use a digital LED driver directly. Many LED drivers have just a shift register input, so they are supported out of the box using the VULEDs class and the SPIShiftRegisterOut class.

The FastLED versions of the VU and VPot classes are not something that I have planned at the moment, because I think that's something that's best handled by the user him/herself. This is easy enough using the same technique as the code snipped above for the VU PWM output. This gives the user full control over which LEDs to use, which colors to use, etc. I don't really like the design of the custom color mappers and index permuters that are used for the NoteFastLED classes (https://tttapa.github.io/Control-Surface-doc/new-input/Doxygen/d9/d6d/10_8Note-FastLED-ColorMapper_8ino-example.html), I think it's too complicated to be useful, and still not flexible enough for many use cases.

74HC165 support is something that I intend to add eventually, but I'll have to order some so I can test it. Personally, I prefer using multiplexers, because the 74HC165 requires a pull-up resistor on each input pin, whereas a multiplexer can just use a single pull-up internal to the microcontroller.

oscarosky commented 3 years ago

The code of your example does not work for me :

'class CS::MCU::VU' has no member named 'getDirty'

tttapa commented 3 years ago

You have to checkout the new-input feature branch for it to work.

oscarosky commented 3 years ago

What do I have to verify exactly?

tttapa commented 3 years ago

What do you mean by “verify”? You have to git fetch && git checkout new-input if you installed the library using Git, or you'll have to remove the version you have installed and reinstall it from the new-input branch.

oscarosky commented 3 years ago

okay thanks. I did not know how to access the new branch https://github.com/tttapa/Control-Surface/tree/new-input

oscarosky commented 3 years ago

I am finding some changes in the new library and I am updating my code. But in the LCDDisplay constructor it doesn't allow me to define the bank and the line simultaneously. I found this in LCDDIsplay.hpp

 LCDDisplay(DisplayInterface &display, MCU::LCD<> &lcd,
                const OutputBank &bank, uint8_t track, PixelLocation loc,
                uint8_t textSize, uint16_t color)
         : DisplayElement(display), lcd(lcd), bank(&bank), track(track - 1),
           line(1), x(loc.x), y(loc.y), size(textSize), color(color) {}
tttapa commented 3 years ago

I've added the constructor with both the bank and line argument. If you used Git to install it, you can just do git pull.

oscarosky commented 3 years ago

great, thanks

oscarosky commented 3 years ago

Hi, at the moment the vumeters with FastLED work correctly ,

#include <FastLED.h>
#include <Control_Surface.h>

#define F_PIN1  3
#define F_PIN2  4

#define NUM_LEDS    12
#define LED_TYPE    WS2812
#define COLOR_ORDER GRB
#define BANKS 4 

USBMIDI_Interface midi;
const int tracks = 8/BANKS;

// Banks conf.
Bank<BANKS> bank(8/BANKS);  
IncrementDecrementSelector<BANKS> bankselector = {bank, {7, 8}, Wrap::Wrap};

// VU meters
unsigned int decayTime = MCU::VUDecay::Default ;
MCU::Bankable::VU<BANKS> vu[] = {
  {bank, 1,decayTime},
  {bank, 2,decayTime},
}; 

//---- FastLED config. ---//

DEFINE_GRADIENT_PALETTE(bgyr_gp)         // Gradient Palette for FastLED
{
    0,   0,  0,200,
   80,   0,200,  0,
  176,  80, 80,  0,
  230, 200,  0,  0, 
  255, 220,  0,  0  
};

CRGB leds1[NUM_LEDS];
CRGB leds2[NUM_LEDS];

uint8_t gBrightness = 70;
CRGBPalette16 currentPalette;
TBlendType    currentBlending;

void FastLEDsetup() {  
  FastLED.addLeds<LED_TYPE, F_PIN1, COLOR_ORDER>(leds1, NUM_LEDS).setCorrection(TypicalLEDStrip);
  FastLED.addLeds<LED_TYPE, F_PIN2, COLOR_ORDER>(leds2, NUM_LEDS).setCorrection(TypicalLEDStrip);

  FastLED.setBrightness(gBrightness);
  currentBlending = LINEARBLEND;
  currentPalette = bgyr_gp;
} 

void updateVU(){
  FastLED.clear();
  for (int i = 0; i < tracks; i++)
  {
    if (vu[i].getDirty()) 
    {
        for(int pix = 0; pix < vu[0].getValue(); pix++) 
        { 
          leds1 [pix] = ColorFromPalette( currentPalette, pix*(255/NUM_LEDS), gBrightness, currentBlending);                       
        }
        for(int pix = 0; pix < vu[1].getValue(); pix++) 
        { 
          leds2 [pix] = ColorFromPalette( currentPalette, pix*(255/NUM_LEDS), gBrightness, currentBlending);                                    
        }
      vu[i].clearDirty();      
      FastLED.show();
    }
  }   
}

void setup() {  
  FastLEDsetup();
  Control_Surface.begin();
}

void loop() {
  Control_Surface.loop();
  updateVU(); 
}

Do you think this method is correct?

tttapa commented 3 years ago

That looks alright to me. You don't really need to clear all LED strips, you can just overwrite the pixels that changed, and I'd probably change the for loop structure a bit, but it should work as far as I can see.

oscarosky commented 3 years ago

Hi, I have modified the loop as I can. Thanks for the suggestion. I have also incorporated a peak retainer.

void updateVU() {
  if (digitalRead(PEAK_RESET) == LOW)
  {
    delay(100);
    peakReset();
  }
  for (uint8_t i = 0; i < tracks; i++)
  {
    if (vu[i].getDirty())
    {
      for (uint8_t pix = 0; pix < NUM_LEDS - 1 ; pix++)
      {
        if ( pix < vu[i].getValue())
        {
          leds[i] [pix] = ColorFromPalette( currentPalette, pix * (255 / NUM_LEDS - 1), gBrightness, currentBlending);
        }
        else if ( pix >= vu[i].getValue())
        {
          leds[i] [pix] = CRGB::Black;
        }
      }
      vu[i].clearDirty();
      updatePeak(vu[i].getValue(), i);
      leds[i] [peak[i]] = ColorFromPalette( currentPalette, peak[i] * (255 / NUM_LEDS), gBrightness, currentBlending);
      FastLED.show();     // probar en otra posicion fuera del bucle y analizar diferencias
    }
  }
}
void updatePeak(uint8_t value, uint8_t i) {
  uint8_t newPeak = value;
  if (newPeak >= peak[i])
  {
    peak[i] = newPeak;
  }
}
void peakReset() {
  for (uint8_t i = 0; i < tracks; i++)
  {
    peak[i] = 0;
  }
  delay (100);
  FastLED.clear();
  FastLED.show();
}

Similarly I am using FastLED for VPotRing

void updateVPotRing(){

  for (uint8_t i = 0; i < tracks; i++)
  {
    if (vpot[i].getDirty()) 
    {      
        for(uint8_t seg = 0; seg < NUM_LEDS_RING ; seg++) 
        { 
          if ( seg >= vpot[i].getStartOn() && seg < vpot[i].getStartOff() )
          {
            ledsRing[i] [seg] = CRGB::Red ; 
          }
          else 
          {
            ledsRing[i] [seg] = CRGB::Black; 
          }
        }
      vpot[i].clearDirty();      
      FastLED.show();
    }
  }   
}

But as I already mentioned, my idea was to be able to change the color of the rings depending on the assignment they have (TRACK, SEND, PAN, EQ ...etc.). Would it be possible to get the current status of the VPot assignment to be able to make these color changes?

oscarosky commented 3 years ago

I am using the NoteButton class for switches

NoteButton buttons[] = {
  {mux.pin(0), MCU::ASSIGN_PAN},
  {mux.pin(1), MCU::ASSIGN_EQ},
  {mux.pin(2), MCU::ASSIGN_PLUGIN},
  {mux.pin(3), MCU::ASSIGN_SEND},
  {mux.pin(4), MCU::ASSIGN_INSTR},    
};
tttapa commented 3 years ago

But as I already mentioned, my idea was to be able to change the color of the rings depending on the assignment they have (TRACK, SEND, PAN, EQ ...etc.). Would it be possible to get the current status of the VPot assignment to be able to make these color changes?

It should be possible. There are two ways that might work: Either listen to the assignment LED updates using the NoteValue class:

NoteValue panAssignment { MCU::ASSIGN_PAN };
...
if (panAssignment.getDirty()) {
  if (panAssignment.getValue() > 0) {
    ...
  }
  panAssignment.clearDirty();
}
// similar for other assignment modes

or check the text on the assignment display.

tttapa commented 3 years ago

You can check what your DAW is sending when changing the assignment by using the Mackie Control Universal Reverse Engineering example.

oscarosky commented 3 years ago

The color only changes when I modify the value with the encoder and it would be appropriate for it to change when pressing the assign switch

...
NoteValue VPotRingAssign [] = { 
  {MCU :: ASSIGN_PAN}, 
  {MCU :: ASSIGN_EQ}, 
  {MCU :: ASSIGN_PLUGIN}, 
  {MCU :: ASSIGN_SEND},
  {MCU :: ASSIGN_INSTR},
};

void updateVPotRing(){
  changeVPotColor();

  for (uint8_t i = 0; i < tracks; i++)
  {
    if (vpot[i].getDirty()) 
    {      
        for(uint8_t seg = 0; seg < NUM_LEDS_RING ; seg++) 
        { 
          if ( seg >= vpot[i].getStartOn() && seg < vpot[i].getStartOff() )
          {
            ledsRing[i] [seg] = colorArray[VPotColor] ; 
          }
          else 
          {
            ledsRing[i] [seg] = CRGB::Black; 
          }
        }
      vpot[i].clearDirty();      
      FastLED.show();
    }
  }   
}

void changeVPotColor(){
  for (uint8_t i = 0; i < 5; i++) 
  {
    if (VPotRingAssign [i].getDirty())
    { 
     if(VPotRingAssign [i].getValue() > 0)
      {
       VPotColor = i;      
      }      
    }
    VPotRingAssign [i].clearDirty();
  }
}  
 ...
oscarosky commented 3 years ago

this is crappy but it works the way i need:

void changeVPotColor(){
       if (digitalRead(mux.pin(0))== LOW) { VPotColor = 0;}
  else if (digitalRead(mux.pin(1))== LOW) { VPotColor = 1;}
  else if (digitalRead(mux.pin(2))== LOW) { VPotColor = 2;}
  else if (digitalRead(mux.pin(3))== LOW) { VPotColor = 3;}
  else if (digitalRead(mux.pin(4))== LOW) { VPotColor = 4;}
};
Jhenesson commented 1 year ago

oscarosky

Hi, Could you share the complete V-pot ring code using FastLed?