Zack Lee's blog

Analog Input and Output with Arduino

Posted at — Oct 4, 2019

Arduino Synth

analogInputOutput

It uses 2 triangle LFO to modulate the pulse-width and pitch. Each button plays 4 notes in sequence and a potentiometer is used to control the tempo.

Todo: add more potentiometers to control note length, pitch, vibrato amount, etc.

Code :

#include <math.h>

/* global variables */
int numAudioOutPin = 9;
int numButton1InputPin = 2;
int numButton2InputPin = 3;
int numButton3InputPin = 4;
int numButton4InputPin = 5;
int numTempoKnobInputPin = A7;

/* classes */
class TriangleLFO
{
    public:
    TriangleLFO(const float speed)
    :speed(speed)
    ,count(0.0f)
    ,bShouldIncrease(true){};
    virtual ~TriangleLFO(){};
    void setSpeed(const float speed)
    {
        this->speed = speed;
    }
    float process()
    {
        if (bShouldIncrease)
        {
            count += speed;
            if (count >= 1.0f) bShouldIncrease = false;
        }
        else
        {
            count -= speed;
            if (count <= -1.0f) bShouldIncrease = true;
        }   
        return count;
    }
private:
    float speed, count;
    bool bShouldIncrease;
};

class Synth
{
public:
    Synth(const float pwmSpeed,      /* between 0 ~ 0.1 */
    const float pwmAmount,     /* between 0 ~ 0.5 */
    const float vibratoSpeed,  /* between 0 ~ 0.1 */
    const float vibratoAmount) /* between 0 ~ 10 */
    :pwm(pwmSpeed)
    ,pwmAmount(pwmAmount)
    ,vibrato(vibratoSpeed)
    ,vibratoAmount(vibratoAmount){};
    virtual ~Synth(){};
    void setPwmSpeed(const float pwmSpeed)
    {
        this->pwm.setSpeed(pwmSpeed);
    }
    void setPwmAmount(const float pwmAmount)
    {
        this->pwmAmount = pwmAmount;
    }
    void setVibratoSpeed(const float vibratoSpeed)
    {
        this->vibrato.setSpeed(vibratoSpeed);
    }
    void setVibratoAmount(const float vibratoAmount)
    {
        this->vibratoAmount = vibratoAmount;
    }
    void play(const float note)
    {
        const float pulseWidth = ((pwm.process() + 1.0f) * 0.5f) * pwmAmount + 0.5f;
        const float frequency = mtof(note) + (vibrato.process() * vibratoAmount);
        const float cycleDurationMillis = 1000.0f / frequency;
        const float highDurationMillis = cycleDurationMillis * pulseWidth;
        const float lowDurationMillis = cycleDurationMillis * (1.0f - pulseWidth);
        unsigned int highDurationMicro = (unsigned int)(highDurationMillis * 1000);
        unsigned int lowDurationMicro = (unsigned int)(lowDurationMillis * 1000);
        digitalWrite(numAudioOutPin, HIGH);
        delayMicroseconds(highDurationMicro);
        digitalWrite(numAudioOutPin, LOW);
        delayMicroseconds(lowDurationMicro);
        }
        void play(const float note, const unsigned long lengthMillis)
        {
            const unsigned long currentTimeMillis = millis();
            const unsigned long elapsedTimeMillis = currentTimeMillis - lastTimeMillis;
            if (elapsedTimeMillis <= lengthMillis)
                play(note);
        }
        void reset()
        {
            lastTimeMillis = millis();
        }
private:
    float mtof(const float note)
    {
        if (note <= -1500) return 0;
        else if (note > 1499) return mtof(1499);
        else return 8.17579891564 * exp(.0577622650 * note);
    }
    TriangleLFO pwm, vibrato;
    float pwmAmount, vibratoAmount;
    unsigned long lastTimeMillis;
};

class Sequencer
{
public:
    Sequencer(const float step1,
              const float step2,
              const float step3,
              const float step4,
              const unsigned long tempoMillis)
    :steps({step1, step2, step3, step4})
    ,tempoMillis(tempoMillis)
    ,lastTimeMillis(millis())
    ,count(0){};
    virtual ~Sequencer(){};
    void setSteps(const float step1,
                  const float step2,
                  const float step3,
                  const float step4)
    {
        this->steps[0] = step1;
        this->steps[1] = step2;
        this->steps[2] = step3;
        this->steps[3] = step4;
    }
    void setTempoMillis(const unsigned long tempoMillis)
    {
        this->tempoMillis = tempoMillis;
    }
    float play()
    {
        const unsigned long currentTimeMillis = millis();
        const unsigned long elapsedTimeMillis = currentTimeMillis - lastTimeMillis;
        if (elapsedTimeMillis > tempoMillis)
        {
            count = (count + 1) % 4;
            lastTimeMillis = currentTimeMillis;
        }
        return steps[count];
    }
    unsigned int getCount()
    {
        return count;
    }
    void reset()
    {
        lastTimeMillis = millis();
        count = 0;
    }
private:
    float steps[4];
    unsigned long tempoMillis;
    unsigned long lastTimeMillis;
    unsigned int count;
};

Synth *synth;
Sequencer *sequencer;
unsigned long tempoMillis = 150;
float noteLength = 0.75;
float noteLengthMillis = tempoMillis * noteLength;
int lastCount = -1;
int lastPressedButton = 0;

void setup()
{
    Serial.begin(9600);
    pinMode(numAudioOutPin, OUTPUT);
    pinMode(numButton1InputPin, INPUT);
    synth = new Synth(0.002f, 0.497f, 0.08f, 4);
    sequencer = new Sequencer(43, 50, 55, 58, tempoMillis);
}

void loop()
{
    int tempoKnobValue = analogRead(numTempoKnobInputPin);
    float tempoMillis = static_cast<float>(tempoKnobValue) / 1023 * 300;
    sequencer->setTempoMillis(static_cast<unsigned long>(tempoMillis));
    noteLengthMillis = tempoMillis * noteLength;

    const int button1State = digitalRead(numButton1InputPin);
    const int button2State = digitalRead(numButton2InputPin);
    const int button3State = digitalRead(numButton3InputPin);
    const int button4State = digitalRead(numButton4InputPin);

    int currentPressedButton = 0;
    bool bAnyButtonPressed = false;

    if (button1State == HIGH)
    {
        sequencer->setSteps(43, 50, 55, 58);
        currentPressedButton = 1;
        bAnyButtonPressed = true;
    }
    else if (button2State == HIGH)
    {
        sequencer->setSteps(46, 50, 53, 58);
        currentPressedButton = 2;
        bAnyButtonPressed = true;
    }
    else if (button3State == HIGH)
    {
        sequencer->setSteps(48, 51, 55, 60);
        currentPressedButton = 3;
        bAnyButtonPressed = true;
    }
    else if (button4State == HIGH)
    {
        sequencer->setSteps(50, 53, 57, 62);
        currentPressedButton = 4;
        bAnyButtonPressed = true;
    }
    if (lastPressedButton != currentPressedButton)
    {
        sequencer->reset();
        lastPressedButton = currentPressedButton;
    }
    if (bAnyButtonPressed)
    {
        const float note = sequencer->play();
        const unsigned int count = sequencer->getCount();
        if (lastCount != count)
        {
            synth->reset();
            lastCount = count;
        }
        synth->play(note, noteLengthMillis);
    }
}

Video :

comments powered by Disqus