🔍

The Keyboard Synthesizer aka another minimalistic TEENSY synth

The TEENSY microcontroller is a special device because of it’s very powerful lib written for it – the Teensy Audio Library which makes it possible to create a respectable variety of synthetic sound with a very minimal effort of electronics and coding.

USING PURE DATA TO SIMULATE THE SYNTH

PureData is one ancient, but still working, node based program we an use to simulate our audio setup instantly.

VERSION II WITH KEYBOARD AND NOKIA 5110

THis version uses an old PS2 Keyboard as input device


#include <Arduino.h>
#include <PS2Keyboard.h>
#include <audio_lib.h>


// ---------------------------------
// Include the library
#include <NOKIA5110_TEXT.h>

// LCD Nokia 5110 pinout left to right
// RST / CE / DC / DIN / CLK / VCC/ LIGHT / GND

#define RST 14 // Reset pin
#define CE 15 // Chip enable
#define DC 16 // data or command
#define DIN 17 // Serial Data input
#define CLK 18 // Serial clock

// Create an LCD object
NOKIA5110_TEXT mylcd(RST, CE, DC, DIN, CLK);

#define inverse false
#define contrast 0xBF // default is 0xBF set in LCDinit, Try 0xB1 - 0xBF if your display is too dark/dim
#define bias 0x13 // LCD bias mode 1:48: Try 0x12 , 0x13 or 0x14

#define display_LED_PIN  19


const int DataPin = 8;
const int IRQpin =  5;

PS2Keyboard keyboard;

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioSynthWaveform       waveform2; //xy=731.0000114440918,560.0000085830688
AudioSynthWaveform       waveform3; //xy=737.0000114440918,641.0000095367432
AudioSynthWaveform       waveform1;      //xy=744.0000076293945,494.00000762939453
AudioMixer4              mixer1;         //xy=1019.0000152587891,568.0000076293945
AudioAmplifier           amp1;           //xy=1177.000015258789,566.0000085830688
AudioEffectEnvelope      envelope1;      //xy=1382.0000228881836,580.0000076293945
AudioEffectFreeverb      freeverb1;      //xy=1593.0000228881836,584.0000085830688
AudioOutputAnalog        dac1;           //xy=1840.0000228881836,572.0000076293945
AudioConnection          patchCord1(waveform2, 0, mixer1, 1);
AudioConnection          patchCord2(waveform3, 0, mixer1, 2);
AudioConnection          patchCord3(waveform1, 0, mixer1, 0);
AudioConnection          patchCord4(mixer1, amp1);
AudioConnection          patchCord5(amp1, envelope1);
AudioConnection          patchCord6(envelope1, freeverb1);
AudioConnection          patchCord7(freeverb1, dac1);
// GUItool: end automatically generated code



int f1=222;
int f2=222;
int f3=222;

int octave = -24;

int note_cnt = 0;

byte size_of_keyboard_buffer = 12;

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


void oscPlay(byte note) {
  waveform1.frequency(noteFreqs[note]);
  waveform2.frequency(noteFreqs[note]);
  waveform3.frequency(noteFreqs[note]);
  float velo = 111;
  waveform1.amplitude(velo);
  waveform2.amplitude(velo);
  waveform3.amplitude(velo);
  //pink1.amplitude(velo);
    envelope1.noteOn(); 

    
}

void oscStop() {
  waveform1.amplitude(0.0);
  waveform2.amplitude(0.0);
   waveform3.amplitude(0.0);
  //pink1.amplitude(0.0);
   envelope1.noteOff(); 
}


void keyBuff(byte note, bool playNote) {
  static byte buff[BUFFER];
  static byte buffSize = 0;

  // Add Note
  if (playNote == true && (buffSize < BUFFER) ) {
     oscPlay(note);
    buff[buffSize] = note;
    buffSize++;
    return;
  }

  // Remove Note
  else if (playNote == false && buffSize != 0) {
    for (byte found = 0; found < buffSize; found++) {
      if (buff[found] == note) {
        for (byte gap = found; gap < (buffSize - 1); gap++) {
          buff[gap] = buff[gap + 1];
        }
        buffSize--;
        buff[buffSize] = 255;
        if (buffSize != 0) {
           //oscPlay(buff[buffSize - 1]);
          return;
        }
        else {
           oscStop();
          return;
        }
      }
    }
  }
}







void playNote(int _noteid){
 
    keyBuff(_noteid, true);
 
}


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

 



void setup() {


   mylcd.LCDInit(inverse, contrast, bias); // init  the LCD
 // mylcd.LCDClear(0x00); // Clear whole screen
  mylcd.LCDFont(LCDFont_Default); // Set the font

  //mylcd.LCD
 mylcd.LCDgotoXY(0, 0); // (go to (X , Y) (0-84 columns, 0-5 blocks) top left corner
  mylcd.LCDString("~ INIT SYNTH ~"); //print
  
  pinMode(display_LED_PIN,OUTPUT);
  digitalWrite(display_LED_PIN,HIGH);

  delay(100);

  //Serial.println("TRY Keyboard Test:");
  keyboard.begin(DataPin, IRQpin, PS2Keymap_German);

   Serial.begin(9600);
  Serial.println("Keyboard Test:");

   AudioMemory(30);
 waveform1.begin(WAVEFORM_SAWTOOTH);
  waveform2.begin(WAVEFORM_SAWTOOTH);
  waveform3.begin(WAVEFORM_SQUARE);
 
  waveform1.amplitude(.83);
  waveform2.amplitude(.83);
  waveform3.amplitude(.83);
 
  waveform1.frequency(f1);
  waveform2.frequency(f2);
  waveform3.frequency(f3);
 
  waveform1.pulseWidth(.2);
  waveform2.pulseWidth(.33);
  waveform3.pulseWidth(.44);


  envelope1.attack(9.2);
  envelope1.hold(122.1);
  envelope1.decay(131.4);
   envelope1.sustain(0.8);
  envelope1.release(424.5);

mixer1.gain(0,.99);
mixer1.gain(1,.99);
mixer1.gain(2,.99);

amp1.gain(.9);

 freeverb1.roomsize(.5);
 freeverb1.damping(.5);

 
}


bool apressed = false;

int keys[12]; 

int triggval = 10;

int keys_vals[12] = {60,62,64,65,67,69,71,0,0,0,0};

void loop() {

apressed = false;  
  if (keyboard.available()) {


    
     
    // read the next key
    char c = keyboard.read();


    if(c == 'a'){playNote(keys_vals[0]+octave); keys[0]=triggval ;}// C
    if(c == 's'){playNote(keys_vals[1]+octave); keys[1]=triggval ;}// D
    if(c == 'd'){playNote(keys_vals[2]+octave); keys[2]=triggval ;} // E
    if(c == 'f'){playNote(keys_vals[3]+octave); keys[3]=triggval ;} // F
    if(c == 'g'){playNote(keys_vals[4]+octave); keys[4]=triggval ;} // G
    if(c == 'h'){playNote(keys_vals[5]+octave); keys[5]=triggval ;} // A
    if(c == 'j'){playNote(keys_vals[6]+octave); keys[6]=triggval ;} // H

    if(c == '1'){ octave += 12;}//  next octave
    if(c == '2'){ octave -= 12;}//  next octave
   
    //note_cnt = 0;

     
    /*
    // check for some of the special keys
    if (c == PS2_ENTER) {
      Serial.println();
    } else if (c == PS2_TAB) {
      Serial.print("[Tab]");
    } else if (c == PS2_ESC) {
      Serial.print("[ESC]");
    } else if (c == PS2_PAGEDOWN) {
      Serial.print("[PgDn]");
    } else if (c == PS2_PAGEUP) {
      Serial.print("[PgUp]");
    } else if (c == PS2_LEFTARROW) {
      Serial.print("[Left]");
    } else if (c == PS2_RIGHTARROW) {
      Serial.print("[Right]");
    } else if (c == PS2_UPARROW) {
      Serial.print("[Up]");
    } else if (c == PS2_DOWNARROW) {
      Serial.print("[Down]");
    } else if (c == PS2_DELETE) {
      Serial.print("[Del]");
    } else {
      
      // otherwise, just print all normal characters
      
    }

    */

   //Serial.print(c);
  }

 
mylcd.LCDClear(0x00); // Clear whole screen

mylcd.LCDgotoXY(20, 60); 
mylcd.LCDString("PLAYMODE");
 for(int i=0;i<12;i++){

    
    if(keys[i]<=1){
      keyBuff(keys_vals[i]+octave, false);
     // Serial.print("turnoff: ");
     // Serial.println(keys_vals[i]+octave);
    }      

    if(keys[i]>0){

      //mylcd.LCDgotoXY(i, 2);
    //  mylcd.LCDSetPixel(i*4,10);

       mylcd.LCDgotoXY(i*10, 0); // (go to (X , Y) (0-84 columns, 0-5 blocks) top left corner
       //mylcd.LCDString("o"); //print
       mylcd.LCDCustomChar(FACE, sizeof(FACE) / sizeof(unsigned char), LCDPadding_None, true);
           
      keys[i]--;
    }

 }

 // 


 
  delay(10);
}






INITAL VERSION WITH RGB RING

#include <Arduino.h>

#include <trxy_btn.h>
#include <Adafruit_NeoPixel.h>


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


#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

 
AudioSynthWaveform       waveform1;      //xy=755.0000114440918,513.0000076293945
AudioSynthWaveform       waveform3; //xy=759,647
AudioSynthWaveform       waveform2; //xy=760.0000076293945,574.0000095367432
AudioMixer4              mixer1;         //xy=1019.0000152587891,586.0000095367432
AudioOutputAnalog        dac1;           //xy=1255.000015258789,567.0000095367432
AudioConnection          patchCord1(waveform1, 0, mixer1, 0);
AudioConnection          patchCord2(waveform3, 0, mixer1, 2);
AudioConnection          patchCord3(waveform2, 0, mixer1, 1);
AudioConnection          patchCord4(mixer1, dac1);
 



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


#define PIN  6//LED RING PIN

 
Adafruit_NeoPixel pixels(12, PIN, NEO_GRB + NEO_KHZ800);

#define ENCODER_DO_NOT_USE_INTERRUPTS
#include <Encoder.h>

#define ENC1 0
#define ENC2 1
 

aBTN MAINBTN(4,false);

aBTN PLAYBTN(7,false);
 
Encoder myEnc(ENC1, ENC2);
int led = 13;


int f1=444;
int f2=444;
int f3=444;

//   avoid using pins with LEDs attached

void setup() {


  AudioMemory(20);
  waveform1.begin(WAVEFORM_SINE);
  waveform2.begin(WAVEFORM_SQUARE);
  waveform3.begin(WAVEFORM_TRIANGLE);

  waveform1.amplitude(.1);
  waveform2.amplitude(.1);
  waveform3.amplitude(.1);

  waveform1.frequency(f1);
  waveform2.frequency(f2);
  waveform3.frequency(f3);

  waveform1.pulseWidth(.2);
  waveform2.pulseWidth(.33);
  waveform3.pulseWidth(.44);



  pinMode(ENC1,INPUT);
  pinMode(ENC2,INPUT);
 
  Serial.begin(9600);
  //while(!Serial); // wait for user to connect...
  //Serial.println("Basic NoInterrupts Test:");
  pinMode(led, OUTPUT);

   pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)  


  

}

int position  = 0;
int mainmode = 0;

//float _radi_pos = 0;
int curr_pos = 0;

elapsedMillis dbad;
uint8_t led_state=0;

int intensi = 22;


void loop() {


   


  long newPos = myEnc.read();
  
   MAINBTN.operateBUTTON();
   PLAYBTN.operateBUTTON();
  
  if (newPos != position) {
    position = newPos;
    Serial.println(position);
    digitalWrite(led,HIGH);
  }else{

    digitalWrite(led,LOW);
  }
   

   curr_pos = int(sin(position*.03)*6)+6;

   for(int i=0;i<12;i++){

      pixels.setPixelColor(  i,  pixels.Color(0,0,0)); 
   }
 
   

  if(mainmode==0){

      pixels.setPixelColor(  curr_pos,  pixels.Color(intensi,0,0)); 

      f1 = curr_pos*100;
  }

   if(mainmode==1){

      pixels.setPixelColor(  curr_pos,  pixels.Color(0,intensi,0)); 

      f2 = curr_pos*100;
  }


  if(mainmode==2){

      pixels.setPixelColor(  curr_pos,  pixels.Color(0,0,intensi)); 
      f3 = curr_pos*100;
  }

  pixels.show();


  if(PLAYBTN.is_pressed){

    intensi = 255;

  }else{

    intensi = 25;
  }

  if(MAINBTN.on_pressed){

      mainmode++;
      if(mainmode>2){
        mainmode=0;
      }

  }

  // Serial.println(int(position));

    waveform1.frequency(f1);
  waveform2.frequency(f2);
  waveform3.frequency(f3);


  // With any substantial delay added, Encoder can only track
  // very slow motion.  You may uncomment this line to see
  // how badly a delay affects your encoder.
    delay(2);
}
The most minimal Synthesizer to build walls of sound 🙂

Are we building a custom synthesizer, really?

Along with the Teensy Microcontroller comes a fantastic library that manages all the audio. It seems insane, but they offer a public online „wiring“ generator called Audio System Design Tool that produces all the code needed to get some sound out of the box 🙂

There is some tutorial videos for beginner out there – here is one to begin with to get some idea for the basics

What’s the input?

For controlling the synth we have a endless range of possibilities from simple button input, matricies, sensors or even generative algorithms. For this example, we will transform a used keyboard into an input device for the synth.

https://www.pjrc.com/teensy/td_libs_PS2Keyboard.html
https://www.youtube.com/watch?v=PmJlDlI-Pb4

tbc.