Cynthcart (c64) midi control

Last Updated or created 2025-09-05

I bought a teensyrom a while ago.

UPDATE 20250712 : Display
UPDATE 20250904 : Added PitchBend and stabilized potmeters.

Tyrone and I wanted to control the settings using potmeters.
So I grabbed a Teensy 4.1 controller and some 10K potmeters, and worte some code

Code for 12 pots, pitch bend and display, don’t forget to set your USB mode to midi!
Schematic soon, also all tweaks and note sending.

Todo: Nice 3D printed Pitch Bend wheel, rest of display code and extra buttons!

Let’s use an old box to hold the pots!

3D printed wooden knobs (Yes wood filament)
Unstable release pot due too bad wires. Cynthcart has no decay and hold (hold is how long you are pressing key)

Knob 1 = volume now, and left of box has centering Pitch Bend.


CODE

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

const int pitchbend = A14;
const int filterresonance = A0;
const int filtercutoff = A1;
const int voicemode = A2;
const int modulationmode = A3;
const int attack = A4;
const int release = A5;
const int pulsewidth = A6;
const int tremolodepth = A7;
const int tremolospeed = A8;
const int oscwave = A9;
const int oscvoice2 = A10;
const int oscvoice3 = A11;
const int volume = A12;

ResponsiveAnalogRead analogpitchbend(pitchbend, true);

ResponsiveAnalogRead analogfilterresonance(filterresonance, true);
ResponsiveAnalogRead analogfiltercutoff(filtercutoff, true);
ResponsiveAnalogRead analogvoicemode(voicemode, true);
ResponsiveAnalogRead analogmodulationmode(modulationmode, true);
ResponsiveAnalogRead analogattack(attack, true);
ResponsiveAnalogRead analogrelease(release, true);
ResponsiveAnalogRead analogpulsewidth(pulsewidth, true);
ResponsiveAnalogRead analogtremolodepth(tremolodepth, true);
ResponsiveAnalogRead analogtremolospeed(tremolospeed, true);
ResponsiveAnalogRead analogoscwave(oscwave, true);
ResponsiveAnalogRead analogoscvoice2(oscvoice2, true);
ResponsiveAnalogRead analogoscvoice3(oscvoice3, true);
ResponsiveAnalogRead analogvolume(volume, true);


// A0 -> A3, A6 -> A13
const int numPots = 12;


int lastfilterresonance = -1;
int lastfiltercutoff = -1;
int lastvoicemode = -1;
int lastmodulationmode = -1;
int lastattack = -1;
int lastrelease = -1;
int lastpulsewidth = -1;
int lasttremolodepth = -1;
int lasttremolospeed = -1;
int lastoscwave = -1;
int lastoscvoice2 = -1;
int lastoscvoice3 = -1;
int lastvolume = -1;


// CC 7 ff niet

// Custom mappings:
const int potPins[numPots]     = {A0, A1, A2, A3, A6, A7, A8, A9, A10, A11, A12, A13};  // Analog pins
const int ccNumbers[numPots]   = {0,1,2,3,4,5,6,8,9,13,14,15};             // CC numbers
const int midiChannels[numPots]= {1,1,1,1,1,1,1,1,1,1,1,1};                // MIDI channels (1–16)

int lastValues[numPots];  // Store last values to reduce redundant MIDI messages


void setup() {
  Serial.begin(9600);
  for (int i = 0; i < numPots; i++) {
    pinMode(potPins[i], INPUT);
    lastValues[i] = -1;
  }
  pinMode(pitchbend, INPUT);
}

void loop() {
  analogfilterresonance.update();
  if(analogfilterresonance.hasChanged()) {
    int analogValue = analogfilterresonance.getValue();
    int midiValue = analogValue / 8;  // Scale 0–1023 to 0–127
    if (abs(midiValue - lastfilterresonance) > 0){
    usbMIDI.sendControlChange(7, midiValue, 1);
    lastfilterresonance = midiValue;
    }
  }

  analogfiltercutoff.update();
  if(analogfiltercutoff.hasChanged()) {
    int analogValue = analogfiltercutoff.getValue();
    int midiValue = analogValue / 8;  // Scale 0–1023 to 0–127
    if (abs(midiValue - lastvoicemode) > 0){
    usbMIDI.sendControlChange(1, midiValue, 1);
    lastvoicemode = midiValue;
    }  
  }

  analogvoicemode.update();
  if(analogvoicemode.hasChanged()) {
    int analogValue = analogvoicemode.getValue();
    int midiValue = analogValue / 8;  // Scale 0–1023 to 0–127
    if (abs(midiValue - lastvoicemode) > 0){
    usbMIDI.sendControlChange(2, midiValue, 1);
    lastvoicemode = midiValue;
    }
  }

  analogmodulationmode.update();
  if(analogmodulationmode.hasChanged()) {
    int analogValue = analogmodulationmode.getValue();
    int midiValue = analogValue / 8;  // Scale 0–1023 to 0–127
    if (abs(midiValue - lastmodulationmode) > 0){
    usbMIDI.sendControlChange(3, midiValue, 1);
    lastmodulationmode = midiValue;
    }
  }


  analogattack.update();
  if(analogattack.hasChanged()) {
    int analogValue = analogattack.getValue();
    int midiValue = analogValue / 8;  // Scale 0–1023 to 0–127
    if (abs(midiValue - lastattack) > 0){
    usbMIDI.sendControlChange(4, midiValue, 1);
    lastattack = midiValue;
    }
  }


  analogrelease.update();
  if(analogrelease.hasChanged()) {
    int analogValue = analogrelease.getValue();
    int midiValue = analogValue / 8;  // Scale 0–1023 to 0–127
    if (abs(midiValue - lastrelease) > 0){
    usbMIDI.sendControlChange(5, midiValue, 1);
    lastrelease = midiValue;
    }
  }


  analogpulsewidth.update();
  if(analogpulsewidth.hasChanged()) {
    int analogValue = analogpulsewidth.getValue();
    int midiValue = analogValue / 8;  // Scale 0–1023 to 0–127
    if (abs(midiValue - lastpulsewidth) > 0){
    usbMIDI.sendControlChange(6, midiValue, 1);
    lastpulsewidth = midiValue;
    }
  }


  analogtremolodepth.update();
  if(analogtremolodepth.hasChanged()) {
    int analogValue = analogtremolodepth.getValue();
    int midiValue = analogValue / 8;  // Scale 0–1023 to 0–127
    if (abs(midiValue - lasttremolodepth) > 0){
    usbMIDI.sendControlChange(8, midiValue, 1);
    lasttremolodepth = midiValue;
    }
  }


  analogtremolospeed.update();
  if(analogtremolospeed.hasChanged()) {
    int analogValue = analogtremolospeed.getValue();
    int midiValue = analogValue / 8;  // Scale 0–1023 to 0–127
    if (abs(midiValue - lasttremolospeed) > 0){
    usbMIDI.sendControlChange(9, midiValue, 1);
    lasttremolospeed = midiValue;
    }
  }


  analogoscwave.update();
  if(analogoscwave.hasChanged()) {
    int analogValue = analogoscwave.getValue();
    int midiValue = analogValue / 8;  // Scale 0–1023 to 0–127
    if (abs(midiValue - lastoscwave) > 0){
    usbMIDI.sendControlChange(13, midiValue, 1);
    lastoscwave = midiValue;
    }
  }


  analogoscvoice2.update();
  if(analogoscvoice2.hasChanged()) {
    int analogValue = analogoscvoice2.getValue();
    int midiValue = analogValue / 8;  // Scale 0–1023 to 0–127
    if (abs(midiValue - lastoscvoice2) > 0){
    usbMIDI.sendControlChange(14, midiValue, 1);
    lastoscvoice2 = midiValue;
    }
  }


  analogoscvoice3.update();
  if(analogoscvoice3.hasChanged()) {
    int analogValue = analogoscvoice3.getValue();
    int midiValue = analogValue / 8;  // Scale 0–1023 to 0–127
    if (abs(midiValue - lastoscvoice3) > 0){
    usbMIDI.sendControlChange(15, midiValue, 1);
    lastoscvoice3 = midiValue;
    }
  }







// PITCHBEND!
  analogpitchbend.update();
  if(analogpitchbend.hasChanged()) {
    int pitchBend = map(analogpitchbend.getValue(), 0, 1023, 0, 16383); // Map to MIDI Pitch Bend range
    sendPitchBend(pitchBend, 0);
  }
  /*
  int potValue = analogRead(A14);         // Read pot (0–1023)
  int pitchBend = map(potValue, 0, 1023, 0, 16383); // Map to MIDI Pitch Bend range
  //pitchBend = pitchBend + 8192;


  if (abs(pitchBend - lastPitch) > 250) { // Send only on significant change
    sendPitchBend(pitchBend, 0); 
        Serial.println(pitchBend);

    lastPitch = pitchBend;
  }
  */


  delay(5);  // CPU-friendly update rate
}

void sendPitchBend(int value, byte channel) {
  byte lsb = value & 0x7F;
  byte msb = (value >> 7) & 0x7F;

  midiEventPacket_t pitchBendPacket = {0x0E, 0xE0 | (channel & 0x0F), lsb, msb};
  MidiUSB.sendMIDI(pitchBendPacket);
  // Needed? 
  MidiUSB.flush();
}

CODE for display

#include <MIDIUSB.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET 4
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// A0 -> A3, A6 -> A13
const int numPots = 12;

// A14 - Pitch bend!
int lastPitch = -1;

//A4 A5 I2C display


// Custom mappings:
const int potPins[numPots]     = {A0, A1, A2, A3, A6, A7, A8, A9, A10, A11, A12, A13};  // Analog pins
const int ccNumbers[numPots]   = {0,1,2,3,4,5,19,7,8,9,13,14};             // CC numbers
const int midiChannels[numPots]= {1,1,1,1,1,1,1,1,1,1,1,1};                // MIDI channels (1–16)

int lastValues[numPots];  // Store last values to reduce redundant MIDI messages

void setup() {
  for (int i = 0; i < numPots; i++) {
    pinMode(potPins[i], INPUT);
    lastValues[i] = -1;
  }
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.clearDisplay();
  display.display();
}

void loop() {
  for (int i = 0; i < numPots; i++) {
    int analogValue = analogRead(potPins[i]);
    int midiValue = analogValue / 8;  // Scale 0–1023 to 0–127

    if (abs(midiValue - lastValues[i]) > 1) {
      usbMIDI.sendControlChange(ccNumbers[i], midiValue, midiChannels[i]);
      lastValues[i] = midiValue;
    }
  }
  int potValue = analogRead(A12);         // Read pot (0–1023)
  int pitchBend = map(potValue, 0, 1023, 0, 16383); // Map to MIDI Pitch Bend range

  if (abs(pitchBend - lastPitch) > 5) { // Send only on significant change
    sendPitchBend(pitchBend, 0); // Channel 1
    lastPitch = pitchBend;
  }

  displayInfo();


  delay(5);  // CPU-friendly update rate
}

void sendPitchBend(int value, byte channel) {
  byte lsb = value & 0x7F;
  byte msb = (value >> 7) & 0x7F;

  midiEventPacket_t pitchBendPacket = {0x0E, 0xE0 | (channel & 0x0F), lsb, msb};
  MidiUSB.sendMIDI(pitchBendPacket);
  // Needed? 
  MidiUSB.flush();
}

void displayInfo(){
 byte x0, y0, x1, y1;     // start/end coordinates for drawing lines on OLED
  display.clearDisplay();
  display.setCursor(0, 0);
  display.print("Attack / Release");

  // draw attack line
  x0 = 0;
  y0 = 63;
  x1 = map(attackParam, 0, 127, 0, ((SCREEN_WIDTH / 4) - 1));
  y1 = 20;
  display.drawLine(x0, y0,  x1,  y1, SH110X_WHITE);

   // draw release line
  x0 = x1;  // start line from previous line's final x,y location
  y0 = y1;
  x1 = x0 + map(releaseParam, 0, 127, 0, ((SCREEN_WIDTH / 4) - 1));
  y1 = 63;
  display.drawLine(x0, y0,  x1,  y1, SH110X_WHITE);

  display.display();

}
Spread the love

Leave a Reply

Your email address will not be published. Required fields are marked *