FortySevenEffects / arduino_midi_library

MIDI for Arduino
MIT License
1.56k stars 252 forks source link

Add pitch bend support to the SimpleSynth example #205

Open franky47 opened 3 years ago

franky47 commented 3 years ago

Original discussion and maths breakdown: https://github.com/FortySevenEffects/arduino_midi_library/discussions/203#discussioncomment-398562

Breakdown of the steps:

Caveats / things to look out for:

nonsuchpro commented 1 year ago

Hey @franky47 Not sure if you remember talking to me about this, I'm the one who recommended adding Pitch Bend to the SimpleSynth. Anyway, I've been playing around with ChatGPT and decided to let it have a go at adding pitch bend to SimpleSynth.ino. It had some success, then it broke it, then a bit more, then broke it again, you get the idea. So after an hour or so and a few different modifications, I feel I came upon my limit on how to convey exactly what it's doing wrong. But there was some progress. Any attempt for a smooth pitch bend never worked out but, we did come up with a stepped or stair case that works with the pitch bend. My main criteria for GPT was that it was NOT to change SimpleSynth.ino's current functionality in any way and just ADD pitch bend functionality and I believe that was a limitation for it. Anytime we deviated from that limitation I imposed just ended up with a broken outcome. Anyway, check out where we're at and maybe tweak a bit if you have time. Maybe some human intervention is in order :)

#include <MIDI.h>
#include "noteList.h"
#include "pitches.h"

MIDI_CREATE_DEFAULT_INSTANCE();

#ifdef ARDUINO_SAM_DUE  // Due has no tone function (yet), overriden to prevent build errors.
#define tone(...)
#define noTone(...)
#endif

// This example shows how to make a simple synth out of an Arduino, using the
// tone() function. It also outputs a gate signal for controlling external
// analog synth components (like envelopes).

static const unsigned sGatePin = 13;
static const unsigned sAudioOutPin = 10;
static const unsigned sMaxNumNotes = 16;
MidiNoteList<sMaxNumNotes> midiNotes;

// Pitch Bend Variables
const int pitchBendRange = 2;  // pitch bend range in semitones
int currentPitchBend = 0;

// -----------------------------------------------------------------------------

inline void handleGateChanged(bool inGateActive) {
  digitalWrite(sGatePin, inGateActive ? HIGH : LOW);
}

inline void pulseGate() {
  handleGateChanged(false);
  delay(1);
  handleGateChanged(true);
}

// -----------------------------------------------------------------------------

void handleNotesChanged(bool isFirstNote = false) {
  if (midiNotes.empty()) {
    handleGateChanged(false);
    noTone(sAudioOutPin);  // Remove to keep oscillator running during envelope release.
  } else {
    // Possible playing modes:
    // Mono Low:  use midiNotes.getLow
    // Mono High: use midiNotes.getHigh
    // Mono Last: use midiNotes.getLast

    byte currentNote = 0;
    if (midiNotes.getLast(currentNote)) {
      // apply pitch bend
      float pitchBendFactor = pow(2.0, (float)currentPitchBend / 12.0);
      int pitchBendFreq = (int)(sNotePitches[currentNote] * pitchBendFactor);

      tone(sAudioOutPin, pitchBendFreq);

      if (isFirstNote) {
        handleGateChanged(true);
      } else {
        pulseGate();  // Retrigger envelopes. Remove for legato effect.
      }
    }
  }
}

// -----------------------------------------------------------------------------

void handleNoteOn(byte inChannel, byte inNote, byte inVelocity) {
  const bool firstNote = midiNotes.empty();
  midiNotes.add(MidiNote(inNote, inVelocity));
  handleNotesChanged(firstNote);
}

void handleNoteOff(byte inChannel, byte inNote, byte inVelocity) {
  midiNotes.remove(inNote);
  handleNotesChanged();
}

void handlePitchBend(byte channel, int bendValue) {
  // convert the pitch bend value to semitones
  float pitchBendSemitones = bendValue / (float)(8192 / pitchBendRange);
  currentPitchBend = pitchBendSemitones;
  handleNotesChanged();
}

// -----------------------------------------------------------------------------

void setup() {
  pinMode(sGatePin, OUTPUT);
  pinMode(sAudioOutPin, OUTPUT);
  MIDI.setHandleNoteOn(handleNoteOn);
  MIDI.setHandleNoteOff(handleNoteOff);
  MIDI.setHandlePitchBend(handlePitchBend);  // set pitch bend handler
  MIDI.begin();
}

void loop() {
  MIDI.read();
}