tttapa / MIDI_controller

This is a library for creating a MIDI controller using an Arduino or Teensy board.
GNU General Public License v3.0
402 stars 70 forks source link

Smoothing pots. #15

Closed NikolajRFA closed 6 years ago

NikolajRFA commented 6 years ago

Having problems with smoothing a potentiometer, when choosing the hires potentiometer exampe i get signal but it seems rather constant... and i really want single values so i dont loose midi info.

I also had to manually include som librarys it couldn't find when i was trying to compile the code. I dont know why this problem keeps happening. Could it be because i dont have the librarys installed in the default folders.

1 pot

Arduino Leonardo

Schematic: ?

Software versions:

MIDI Controller library: 3.0.0
Arduino IDE: ? 1.8.5
Operating System: Windows Operating System version: 10 (Teensyduino): ? 1.40 ? (Encoder library): Where do i find this? (MIDIUSB library): Where do i find this?

Settings in the IDE

Full code

#include <Encoder.h>

/*
This is an example of the "AnalogHiRes" class of the MIDI_controller library.
Connect a potentiometer to analog pin A0. It will send MIDI Pitch Bend messages on channel 1
Map it in your DAW or DJ software.

Written by Pieter P, 08-09-2017
https://github.com/tttapa/MIDI_controller
*/

#include <MIDI_Controller.h> // Include the library

// Create a new instance of the class 'AnalogHiRes', called 'potentiometer', on pin A0, 
// that sends MIDI Pitch Bend messages on channel 1
AnalogHiRes potentiometer(A0, 1);

void setup() {}

void loop() {
  // Refresh the MIDI controller (check whether the potentiometer's input has changed since last time, if so, send the new value over MIDI)
  MIDI_Controller.refresh();
}

i pretty much just put in the encoder library manually as i was getting errors saying that it couldn't find it.

i want to make a MIDI controller to control my DAW (FL Studio).

tttapa commented 6 years ago

As mentioned in the ReadMe, you have to install the Encoder library (even if you don't use it, because of the way C++ handles dependencies). You shouldn't have to add it at the top of your sketch, however.

Are you sure that FL Studio supports Pitch Bend messages as controls? Have you tried the potentiometer example? It uses Control Change events (which are more widely supported).

NikolajRFA commented 6 years ago

The MIDI data works fine, my problem is that the Arduino sends duplicate values over MIDI and I would like it to only send one value... I find the potentiometer example to do this, but it is filled with noise and it is jumping back and forth a value.

tttapa commented 6 years ago

That's rather strange, the running average work just fine for me, it does a great job at eliminating the noise.
AnalogHiRes sending duplicate values is weird as well: https://github.com/tttapa/MIDI_controller/blob/ff983c5c470464edab95af5033ec6f65c6ce47ba/src/MIDI_Outputs/AnalogHiRes.cpp#L20 What kind of duplicate values are you getting? Did you use a MIDI monitor?

NikolajRFA commented 6 years ago

turns out the midi data is just flickering and thereforeit is sending constant messages when i am using the "HiRes-Potentiometer" example. Is there anyway i can use the library to send a variable value so i can use a library i found to send the values?

NikolajRFA commented 6 years ago

I see the code you send me isen't the same as i am using... And i can't get i to work because im missing "analogHiRes.h"

tttapa commented 6 years ago

The code I sent was a snippet from the source code. You don't have to use it yourself. Do you want to use ResponsiveAnalogRead? In that case, you can easily extend the Analog and AnalogHiRes classes with variants that use that as an alternative to a simple running average. If I find the time, I'll write you an example.

NikolajRFA commented 6 years ago

that would be nice as i don't know how to extend the classes, i have only been in the Arduino game for a month or so.

tttapa commented 6 years ago

Untested! Put all three files in the same folder (with the same name as Main.ino).


Main.ino

#include <MIDI_Controller.h> // Include the library
#include "AnalogHiResResponsive.h"

// Create a new instance of the class 'AnalogHiResResponsive', called 'potentiometer', on pin A0, 
// that sends MIDI Pitch Bend messages on channel 1
AnalogHiResResponsive potentiometer(A0, 1);

void setup() {}

void loop() {
  // Refresh the MIDI controller (check whether the potentiometer's input has changed since last time, if so, send the new value over MIDI)
  MIDI_Controller.refresh();
}

AnalogHiResResponsive.h

#ifndef AnalogHiResResponsive_h_
#define AnalogHiResResponsive_h_

#include "Arduino.h"
#include "MIDI_Controller.h"
#include <ResponsiveAnalogRead.h>

class AnalogHiResResponsive : public MIDI_Control_Element
{
  public:
    AnalogHiResResponsive(pin_t analogPin, uint8_t channel); // Constructor
    void map(int (*fn)(int));                        // Change the function pointer for analogMap to a new function. It will be applied to the raw analog input value in Analog::refresh()

  private:
    void refresh(); // Read the analog input value, update the average, map it to a MIDI value, check if it changed since last time, if so, send Pitch Bend message over MIDI

    ResponsiveAnalogRead respAnalog = {0, true};
    pin_t analogPin;
    uint8_t channel;
    int (*analogMap)(int) = identity; // function pointer to identity function f(x) → x

    static int identity(int x) {  // identity function f(x) → x
      return x;
    }
};

#endif // AnalogHiResResponsive_h_

AnalogHiResResponsive.cpp

#include "AnalogHiResResponsive.h"

AnalogHiResResponsive::AnalogHiResResponsive(pin_t analogPin, uint8_t channel) // Constructor
  : analogPin(analogPin), channel(channel) {}

void AnalogHiResResponsive::refresh() // read the analog value, update the average, map it to a MIDI value, check if it changed since last time, if so, send Pitch Bend message over MIDI
{
  ExtIO::analogRead(analogPin); // throw away first analog reading
  analog_t input = ExtIO::analogRead(analogPin);
  uint16_t value = analogMap(input); // apply the analogMap function to the value (identity function f(x) = x by default)
  respAnalog.update(value); // update the responsive analog average

  if (respAnalog.hasChanged()) // if the value changed since last time
  {
    value = respAnalog.getValue(); // get the responsive analog average value
    value = value << 4; // make it a 14-bit number (pad with 4 zeros)
    MIDI_Controller.MIDI()->send(PITCH_BEND, channel + channelOffset * channelsPerBank, value, value >> 7); // send a Pitch Bend MIDI event
  }
}

void AnalogHiResResponsive::map(int (*fn)(int)) {  // change the function pointer for analogMap to a new function. It will be applied to the raw analog input value in refresh()
  analogMap = fn;
}

I just copied the AnalogHiRes.h and AnalogHiRes.cpp source files from the library, renamed the class, changed the includes, deleted the running average functions and variables, added ResponsiveAnalogRead and edited the refresh function to use the responsive analog read average instead of the running average.

NikolajRFA commented 6 years ago

This works really good. Now i just need to figure out how to program the rest of the controller... How will this AnalogHiResResponsive work? do i just add that library together with the MIDI_Controller library to get the "AnalogHiResResponsive"?

NikolajRFA commented 6 years ago

also. How do i adjust it to send messages on other channels not just the pitchbend?

tttapa commented 6 years ago

You don't have to add anything to the library, after you include the header files, you can just use it like the other parts of the library.

You can do the same thing for the Analog class if you need to. What do you need for the rest of the controller?

NikolajRFA commented 6 years ago

I wanted to pu a couple of extra potentiometers on and some buttons

tttapa commented 6 years ago

You can add multiple potentiometers by using arrays:

AnalogHiResResponsive potentiometers[] = {
  {A0, 1},
  {A1, 2},
  {A3, 3},
  // etc.
};

For adding buttons, take a look at the buttons example.

NikolajRFA commented 6 years ago

ohh i see, i will try that.

NikolajRFA commented 6 years ago

i still dont know how to send data over other channels than Pitch Bend.

tttapa commented 6 years ago

That's what the Analog class is for. It sends the data as Control Change events. You can use it as is (works great for me, YMMV), or you can create a new class that uses the ResponsiveAnalogRead.

tttapa commented 6 years ago

Everything should be in the documentation. If you think something is missing, or not clear enough, feel free to let me know :)

NikolajRFA commented 6 years ago

im glad you are helping with this, as i still am reallybad coding the arduino.

NikolajRFA commented 6 years ago

also... what does that "uint8_t" do i am seeing it everywhere that MIDI and arduino is mentioned

tttapa commented 6 years ago

It is a data type. It's an unsigned integer (uint) of 8 bits wide. This means that it can store any value between 0 and 255 (=2^8-1).

tttapa commented 6 years ago

It's used here because MIDI packets are constructed of multiple of these 8-bit bytes.

NikolajRFA commented 6 years ago

i see

NikolajRFA commented 6 years ago

I can't really get it to work by using your library and code... But i tried writing a piece of code with my current knowladge and a bit of googeling, only using the "ResponsiveAnalogRead" and the "MIDIUSB" libraries, and this works

Here is the code if interested.

#include <ResponsiveAnalogRead.h>
#include "MIDIUSB.h"

int val = 0;
int lastVal = 0;

const int Pot = A0;

ResponsiveAnalogRead PotRead(Pot, true);

void setup() {
  Serial.begin(115200);
}

void loop() {
  PotRead.update();

  val = PotRead.getValue()/8;

  midiEventPacket_t event = {0x0B, 0xB0 | 0, 104, val};

  if(val != lastVal)
  {MidiUSB.sendMIDI(event);
   MidiUSB.flush();}
  lastVal = val;

}

I have no idea why your library won't work (probably because i am really bad at coding) the code would probably be better if I could get your library to work...

tttapa commented 6 years ago

Here's what a MIDI controller with potentiometers and buttons would look like using the library:

#include <MIDI_Controller.h> // Include the library
#include "AnalogHiResResponsive.h"

AnalogHiResResponsive hiResPotentiometers[] = { // 10-bit Pitch Bends
  {A0, 1},  // pin A0, MIDI channel 1
  {A1, 2},
  {A2, 3},
  {A3, 4},
};

Analog potentiometers[] = { // 7-bit Control Change
  {A4, 0x20, 1},  // pin A4, controller 0x20, MIDI channel 1
  {A5, 0x21, 1},
};

Digital buttons[] = {  // Note On / Off
  {2, 0x3C, 1},  // pin 2, note 0x3C (middle C), MIDI channel 1
  {3, 0x3D, 1},
  {4, 0x3E, 1},
  {5, 0x3F, 1},
};

void setup() {}

void loop() {
  MIDI_Controller.refresh();
}

You need to have the files "AnalogHiResResponsive.cpp" and "AnalogHiResResponsive.h" in the same folder (press CTRL+SHIFT+N in the IDE, enter the filename "AnalogHiResResponsive.cpp", then paste the code there and save, same for "AnalogHiResResponsive.h").

NikolajRFA commented 6 years ago

The codes is sendinf pitch bend messages together with my other midi messages when i am turning the potentiometers

tttapa commented 6 years ago

Correct.
If you want 10-bit resolution, you have to use pitch bends (AnalogHiRes and AnalogHiResResponsive). If 7 bit is enough (in most cases it is), you can use control change (Analog). The potentiometers on analog pins A0-A3 send pitch bend messages, the potentiometers connected to pins A4-A5 send control change messages. It's up to you to decide which ones you'd like to use. You can make all of them control change, make all of them pitch bends, or mix and match. You don't have to use pitch bends if you don't want to.
You can add as many potentiometers and buttons to the arrays as you'd like, and you can decide what controller numbers, note numbers, MIDI channels ... they use.

NikolajRFA commented 6 years ago

after fiddeling a bit with it i have gotten it to work with the stadart "MIDIUSB" library. FL Studio can't reassign pitchbend messages to control other parameters, which i would have needed for it to have worked.

anyways, thanks for the help.

rabbiccu commented 6 years ago

I think I experienced what this user was talking about. In the examples the channels are different and the CC# is the same. There was crosstalk and the knobs were shaky. When each knob is given its own CC# they become nice and smooth.

/*
This is an example of the "Analog" class of the MIDI_controller library.
Connect a potentiometer to analog pin A0. This will be the MIDI channel volume of channel 1.
Map it in your DAW or DJ software.
Written by Pieter P, 08-09-2017
https://github.com/tttapa/MIDI_controller
*/

#include <MIDI_Controller.h> // Include the library

// Create a new instance of the class 'Analog', called 'potentiometer', on pin A0, 
// that sends MIDI messages with controller 7 (channel volume) on channel 1
Analog potentiometer_A(A0, MIDI_CC::Sound_Controller_1, 1);
Analog potentiometer_B(A1, MIDI_CC::Sound_Controller_2, 1);
Analog potentiometer_C(A2, MIDI_CC::Sound_Controller_3, 1);
Analog potentiometer_D(A3, MIDI_CC::Sound_Controller_4, 1);
Analog potentiometer_E(A4, MIDI_CC::Sound_Controller_5, 1);

void setup() {}

void loop() {
  // Refresh the MIDI controller (check whether the potentiometer's input has changed since last time, if so, send the new value over MIDI)
  MIDI_Controller.refresh();
}
tttapa commented 6 years ago

The master version has significantly improved filtering on the potentiometer inputs. I don't have the time to release it, because updating the documentation takes a lot of work, and I have to level it with the Control Surface library first, but you could consider it as stable for now (1ecbd2c86e2268b36391db0ae5a5390cdbe90b8a).

You can try git checkout origin/master, or download the master files manually.

tttapa commented 6 years ago

By the way, instead of:

Analog potentiometer_A(A0, MIDI_CC::Sound_Controller_1, 1);
Analog potentiometer_B(A1, MIDI_CC::Sound_Controller_2, 1);
Analog potentiometer_C(A2, MIDI_CC::Sound_Controller_3, 1);
Analog potentiometer_D(A3, MIDI_CC::Sound_Controller_4, 1);
Analog potentiometer_E(A4, MIDI_CC::Sound_Controller_5, 1);

You can do:

Analog potentiometers[] = {
  {A0, MIDI_CC::Sound_Controller_1, 1},
  {A1, MIDI_CC::Sound_Controller_2, 1},
  {A2, MIDI_CC::Sound_Controller_3, 1},
  {A3, MIDI_CC::Sound_Controller_4, 1},
  {A4, MIDI_CC::Sound_Controller_5, 1},
};
rabbiccu commented 6 years ago

"You can try git checkout origin/master, or download the master files manually."

How exactly do I do that? This is my first project.

tttapa commented 6 years ago

When installing the library, you downloaded a ZIP file from the releases page. Now, instead of using the ZIP from the releases page, download the ZIP from the main page: image Make sure to delete the old version before you install the new one.