Arnov Sharma
Published © MIT

Pi Synth

A Raspberry Pi PICO powered Synth using CD74HC4067 Multiplexer

BeginnerFull instructions provided2 hours124
Pi Synth

Things used in this project

Hardware components

Raspberry Pi Pico 2
Raspberry Pi Pico 2
×1
SparkFun Analog/Digital MUX Breakout - CD74HC4067
SparkFun Analog/Digital MUX Breakout - CD74HC4067
×1
Push button
×24

Software apps and online services

Fusion
Autodesk Fusion
Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

FRAME

Schematics

WIRING

Code

MAIN CODE

C/C++
#include <math.h>

// ================= MUX =================
#define S0 20
#define S1 19
#define S2 18
#define S3 17
#define SIG 16

// ================= AUDIO =================
#define AUDIO_PIN 21
#define SAMPLE_RATE 22050

// ================= LED =================
#define LED_PIN 22

// ================= RIFF BUTTON =================
#define RIFF_BUTTON 8

#define VOICES 4

// ================= NOTE TABLE =================
float noteTable[12] = {
  261.63,277.18,293.66,311.13,
  329.63,349.23,369.99,392.00,
  415.30,440.00,466.16,523.25
};

// ================= KEY MAP =================
int keyMap[12] = {
  1,3,6,0,11,8,
  9,7,5,4,2,0
};

// ================= SYNTH =================
float phase[VOICES];
float subPhase[VOICES];
float freq[VOICES];
float env[VOICES];
bool voiceActive[VOICES];

float cutoff = 1200;
float lpState = 0;

int octave = 0;

// ================= DISTORTION =================
float distortionAmount = 0;
bool distortionUp = true;
bool distortionFadeOn = false;

unsigned long lastDistUpdate = 0;

#define DIST_FADE_INTERVAL 20
#define DIST_STEP 0.05

// ================= DRUM =================
bool drumOn = false;

unsigned long lastStep = 0;
int stepMs = 500;

float kickEnv = 0;
float kickPhase = 0;

// ================= RIFF =================
float riffNotes[] = {
  329.63,329.63,392.00,329.63,
  293.66,261.63,246.94,220.00
};

int riffLength = 8;
int riffStep = 0;

bool riffPlaying = false;

unsigned long riffTimer = 0;

int riffNoteDuration = 450;

// ================= TIMING =================
unsigned long lastAudio = 0;
unsigned long lastScan = 0;

float ledLevel = 0;


// ================= MUX =================
void selectMux(uint8_t ch)
{
  digitalWrite(S0,(ch&1)?HIGH:LOW);
  digitalWrite(S1,(ch&2)?HIGH:LOW);
  digitalWrite(S2,(ch&4)?HIGH:LOW);
  digitalWrite(S3,(ch&8)?HIGH:LOW);
  delayMicroseconds(5);
}

bool readButton(uint8_t ch)
{
  selectMux(ch);
  return digitalRead(SIG)==LOW;
}


// ================= DISTORTION =================
void handleDistortionFade()
{
  if(!distortionFadeOn) return;

  unsigned long now = millis();

  if(now-lastDistUpdate < DIST_FADE_INTERVAL) return;

  lastDistUpdate = now;

  if(distortionUp)
  {
    distortionAmount += DIST_STEP;

    if(distortionAmount >= 1)
    {
      distortionAmount = 1;
      distortionUp = false;
    }
  }
  else
  {
    distortionAmount -= DIST_STEP;

    if(distortionAmount <= 0)
    {
      distortionAmount = 0;
      distortionUp = true;
    }
  }
}


// ================= SETUP =================
void setup()
{
  pinMode(S0,OUTPUT);
  pinMode(S1,OUTPUT);
  pinMode(S2,OUTPUT);
  pinMode(S3,OUTPUT);

  pinMode(SIG,INPUT_PULLUP);

  pinMode(AUDIO_PIN,OUTPUT);
  pinMode(LED_PIN,OUTPUT);

  pinMode(RIFF_BUTTON,INPUT_PULLUP);

  analogWriteResolution(10);
  analogWriteFreq(62500);

  for(int i=0;i<VOICES;i++)
  {
    voiceActive[i]=false;
    env[i]=0;
  }
}


// ================= LOOP =================
void loop()
{
  unsigned long now = micros();

  handleDistortionFade();

  // ---------- BUTTON SCAN ----------
  if(now-lastScan > 3000)
  {
    lastScan = now;

    bool pressed[12];

    for(int i=0;i<12;i++)
      pressed[i] = readButton(i);

    int v = 0;

    for(int i=0;i<12;i++)
    {
      if(pressed[i] && v<VOICES)
      {
        freq[v] = noteTable[keyMap[i]] * pow(2,octave);
        voiceActive[v] = true;
        v++;
      }
    }

    for(int i=v;i<VOICES;i++)
      voiceActive[i] = false;


    // distortion toggle
    static bool lastDist=false;
    bool distBtn = readButton(12);

    if(!lastDist && distBtn)
      distortionFadeOn = !distortionFadeOn;

    lastDist = distBtn;


    // drum toggle
    static bool lastDr=false;
    bool drumBtn = readButton(13);

    if(!lastDr && drumBtn)
      drumOn = !drumOn;

    lastDr = drumBtn;


    // octave up
    static bool lastOctUp=false;
    bool octUp = readButton(14);

    if(!lastOctUp && octUp)
      octave = min(octave+1,2);

    lastOctUp = octUp;


    // octave down
    static bool lastOctDown=false;
    bool octDown = readButton(15);

    if(!lastOctDown && octDown)
      octave = max(octave-1,-2);

    lastOctDown = octDown;


    // ---------- RIFF TRIGGER ----------
    if(digitalRead(RIFF_BUTTON)==LOW && !riffPlaying)
    {
      riffPlaying = true;
      riffStep = 0;
      riffTimer = millis();
    }
  }


  // ---------- RIFF PLAYER ----------
  if(riffPlaying)
  {
    if(millis() - riffTimer > riffNoteDuration)
    {
      riffTimer = millis();

      freq[0] = riffNotes[riffStep];

      env[0] = 0;
      voiceActive[0] = true;

      riffStep++;

      if(riffStep >= riffLength)
      {
        riffPlaying = false;
        voiceActive[0] = false;
      }
    }
  }


  // ---------- DRUM ----------
  if(drumOn && millis()-lastStep > stepMs)
  {
    lastStep = millis();

    kickEnv = 1.0;
    kickPhase = 0;
  }


  // ---------- CHECK SOUND ----------
  bool anyVoice=false;

  for(int i=0;i<VOICES;i++)
    if(voiceActive[i]) anyVoice=true;

  if(!anyVoice && !drumOn && kickEnv < 0.001)
  {
    analogWrite(AUDIO_PIN,0);
    analogWrite(LED_PIN,0);
    return;
  }


  // ---------- AUDIO ENGINE ----------
  if(now-lastAudio > 1000000/SAMPLE_RATE)
  {
    lastAudio = now;

    float synthOut = 0;
    float drumOut = 0;

    int activeVoices = 0;

    for(int v=0; v<VOICES; v++)
    {
      if(!voiceActive[v]) continue;

      activeVoices++;

      env[v] += (1-env[v])*0.04;

      phase[v] += freq[v]/SAMPLE_RATE;
      if(phase[v]>=1) phase[v]-=1;

      subPhase[v] += freq[v]*0.5/SAMPLE_RATE;
      if(subPhase[v]>=1) subPhase[v]-=1;

      float osc =
        ((phase[v]*2-1)*0.6 +
        ((subPhase[v]<0.5)?0.6:-0.6)*0.4)
        * env[v];

      synthOut += osc;
    }

    if(activeVoices>0)
      synthOut /= activeVoices;

    synthOut *= 1.8;

    float c = 2*sin(PI*cutoff/SAMPLE_RATE);

    lpState += c*(synthOut-lpState);

    synthOut = lpState;


    if(distortionAmount>0.01)
    {
      float drive = 1 + distortionAmount*3.5;

      synthOut *= drive;

      if(synthOut>0.65) synthOut=0.65;
      if(synthOut<-0.65) synthOut=-0.65;
    }


    // ---------- KICK DRUM ----------
    if(kickEnv > 0.001)
    {
      float kickFreq = 65 + kickEnv*120;

      kickPhase += kickFreq/SAMPLE_RATE;

      if(kickPhase > 1) kickPhase -= 1;

      drumOut += sin(2*PI*kickPhase) * kickEnv * 1.5;

      kickEnv *= 0.975;
    }


    float out = synthOut + drumOut;

    out = constrain(out,-1,1);

    analogWrite(AUDIO_PIN,(out+1)*512);


    // ---------- LED AUDIO METER ----------
    float level = fabs(out);

    ledLevel = ledLevel*0.6 + level*0.4;

    int brightness = constrain(ledLevel*1400,0,1023);

    analogWrite(LED_PIN,brightness);
  }
}

Credits

Arnov Sharma
374 projects • 389 followers
I'm Arnov. I build, design, and experiment with tech—3D printing, PCB design, and retro consoles are my jam.

Comments