Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pitch bend support to the SimpleSynth example #205

Open
franky47 opened this issue Feb 26, 2021 · 1 comment
Open

Add pitch bend support to the SimpleSynth example #205

franky47 opened this issue Feb 26, 2021 · 1 comment
Labels
examples Relates to code in Examples good first issue Ideal for first-time open source contributors

Comments

@franky47
Copy link
Member

Original discussion and maths breakdown:
#203 (reply in thread)

Breakdown of the steps:

  • Add a handlePitchBend callback
  • Calculate a multiplicative factor (multiplied by the current note)
  • Update the value passed to tone

Caveats / things to look out for:

  • Note change while having bend on: the multiplicative factor should be a global variable, so that it can be applied too in handleNoteOn and handleNoteOff.
  • Pitch bend range: the second argument of handlePitchBend is an int between 0 (max downwards bend), 0x2000 (neutral position) and 0x3fff (max upwards bend). The maximum bend in semitones should be a static const int constant (for an additional challenge, it could follow a ControlChange).
@franky47 franky47 added examples Relates to code in Examples good first issue Ideal for first-time open source contributors new feature and removed new feature labels Feb 26, 2021
@nonsuchpro
Copy link

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();
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
examples Relates to code in Examples good first issue Ideal for first-time open source contributors
Projects
None yet
Development

No branches or pull requests

2 participants