Skip to content

Instantly share code, notes, and snippets.

@tfry-git
Last active August 6, 2024 17:43

Revisions

  1. tfry-git revised this gist Jan 17, 2021. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion Arduino 7 potentiometer Synth (Mozzi-based)
    Original file line number Diff line number Diff line change
    @@ -125,7 +125,7 @@ void setup(){
    notes[i].env.setADLevels(200,100);
    notes[i].env.setDecayTime(100);
    notes[i].env.setSustainTime(1000);
    notes[i].setTable(WAVE_TABLES[0]);
    notes[i].oscil.setTable(WAVE_TABLES[0]);
    notes[i].note = 0;
    }

  2. tfry-git revised this gist Dec 3, 2020. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions Arduino 7 potentiometer Synth (Mozzi-based)
    Original file line number Diff line number Diff line change
    @@ -125,6 +125,7 @@ void setup(){
    notes[i].env.setADLevels(200,100);
    notes[i].env.setDecayTime(100);
    notes[i].env.setSustainTime(1000);
    notes[i].setTable(WAVE_TABLES[0]);
    notes[i].note = 0;
    }

  3. tfry-git revised this gist Dec 18, 2017. 1 changed file with 5 additions and 3 deletions.
    8 changes: 5 additions & 3 deletions Arduino 7 potentiometer Synth (Mozzi-based)
    Original file line number Diff line number Diff line change
    @@ -38,6 +38,7 @@
    #include <MozziGuts.h>
    #include <Oscil.h>
    #include <mozzi_midi.h>
    #include <mozzi_rand.h>
    #include <ADSR.h>
    #include <LowPassFilter.h>

    @@ -131,6 +132,7 @@ void setup(){
    noteDelay.set (1000);
    #endif

    randSeed();
    startMozzi(CONTROL_RATE); // set a control rate of 64 (powers of 2 please)
    }

    @@ -146,7 +148,7 @@ void readPots() {
    #if defined(FAKE_POTS)
    // Fake potentiometers: Fill with random values
    for (int i = 0; i < POTENTIOMETER_COUNT; ++i) {
    pots[i] = random (1024);
    pots[i] = rand (1024); // Mozzis rand() function is faster than Arduinos random(), and good enough for us.
    }
    pots[LPFCutoffPot] = pots[LPFCutoffPot] >> 1 & (1023); // Lower cutoffs reserved for actual user interaction, as they can turn off sounds, completely.
    pots[LPFResonancePot] = pots[LPFResonancePot] << 1; // Similarly, keep resonance to a safe limit for random parameters
    @@ -209,7 +211,7 @@ void updateControl(){

    #if defined(FAKE_MIDI_IN)
    if (noteDelay.ready ()) {
    MyHandleNoteOn (1, random (20) + 77, 100);
    MyHandleNoteOn (1, rand (20) + 77, 100);
    noteDelay.start (1000);
    }
    #endif
    @@ -235,7 +237,7 @@ int updateAudio(){
    int ret = 0;
    for (byte i = 0; i < NOTECOUNT; ++i) {
    // ret += ((long) notes[i].current_vol * ((notes[i].oscil2.next() * notes[i].osc2_mag + notes[i].oscil.next() * (256u - notes[i].osc2_mag)))) >> 14; // Wave mixing
    ret += ((int) notes[i].current_vol * notes[i].lpf.next(notes[i].oscil.next())) >> 10; // LPF
    ret += ((int) notes[i].current_vol * notes[i].lpf.next(notes[i].oscil.next())) >> 6; // LPF
    }
    return ret;
    }
  4. tfry-git revised this gist Dec 9, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion Arduino 7 potentiometer Synth (Mozzi-based)
    Original file line number Diff line number Diff line change
    @@ -130,7 +130,7 @@ void setup(){
    #if FAKE_MIDI
    noteDelay.set (1000);
    #endif
    p

    startMozzi(CONTROL_RATE); // set a control rate of 64 (powers of 2 please)
    }

  5. tfry-git created this gist Dec 9, 2017.
    288 changes: 288 additions & 0 deletions Arduino 7 potentiometer Synth (Mozzi-based)
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,288 @@
    /* (Not so) simple synth based on Mozzi library and a bunch of pots.
    *
    * This code is derived from the public domain example of an 8 potentiometer
    * synth from e-licktronic (https://www.youtube.com/watch?v=wH-xWqpa9P8).
    *
    * Severely edited for clarity and configurability, adjusted to run with modern
    * versions of Mozzi, extended for polyphony by Thomas Friedrichsmeier. Also,
    * this sketch will auto-generate fake MIDI events and random parameters, so
    * you can start listening without connecting anything other than your
    * headphones or amplifier. (Remove the FAKE_POTS and FAKE_MIDI defines, once
    * you connect to real hardware).
    *
    * Note that this sketch does not use any port multiplexing, thus to use all
    * seven pots, you need a board with seven or more analog inputs. The Arduino
    * Nano seems like a perfect match, but some Pro Mini clones also make A6 and A7
    * available.
    *
    * Circuit: Audio output on digital pin 9 (on a Uno or similar), or
    * check the README or http://sensorium.github.com/Mozzi/
    *
    *
    * Copyright (c) 2017 Thomas Friedrichsmeier
    * This program is free software: you can redistribute it and/or modify
    * it under the terms of the GNU General Public License as published by
    * the Free Software Foundation, either version 3 of the License, or
    * (at your option) any later version.
    *
    * This program is distributed in the hope that it will be useful,
    * but WITHOUT ANY WARRANTY; without even the implied warranty of
    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    * GNU General Public License for more details.
    *
    * You should have received a copy of the GNU General Public License
    * along with this program. If not, see <http://www.gnu.org/licenses/>.
    */

    #include <MIDI.h>
    #include <MozziGuts.h>
    #include <Oscil.h>
    #include <mozzi_midi.h>
    #include <ADSR.h>
    #include <LowPassFilter.h>

    // The waveforms to use. Note that the wavetables should all be the same size (see TABLE_SIZE define, below)
    // Low table sizes (512, here), help to keep the sketch size small, but are not as pure (which may not even be a bad thing)
    #include <tables/sin512_int8.h>
    #include <tables/saw_analogue512_int8.h>
    #include <tables/triangle512_int8.h>
    #include <tables/square_analogue512_int8.h>
    #define NUM_TABLES 4
    const int8_t *WAVE_TABLES[NUM_TABLES] = {SQUARE_ANALOGUE512_DATA, SIN512_DATA, SAW_ANALOGUE512_DATA, TRIANGLE512_DATA};
    #define TABLE_SIZE 512


    // Fake Pots for easy testing w/o hardware. Disable the line below, once you have real pots connected
    #define FAKE_POTS
    // Fake MIDI input for easy testing. Disable the line below, if you have real MIDI in
    #define FAKE_MIDI_IN

    // number of polyphonic notes to handle at most. Increasing this carries the risk of overloading the processor
    // For an ATMEGA328 at 16MHz, 2 will be the limit, or even just one, if you increase the complexity of the synthesis.
    // But on a 32bit CPU, you can up this, considerably!
    #define NOTECOUNT 2

    // Rate (Hz) of calling updateControl(), powers of 2 please.
    #define CONTROL_RATE 64

    // The point of this enum is to provide readable names for the various inputs.
    // For the mapping of analog pins to parameter, see the function readPots(), further below.
    enum Potentiometers {
    WaveFormPot,
    // OctavePot, // Octave of primary oscil. The E-licktronics synth has it, but I did not see the point, and decided to skip this.
    AttackPot,
    ReleasePot,

    /* // Additive Synthesis
    WaveForm2Pot,
    Oscil2OctavePot,
    Oscil2DetunePot,
    Oscil2MagnitudePot, */

    // Modulated LPF
    LPFCutoffPot,
    LPFResonancePot,
    LFOSpeedPot,
    LFOWaveFormPot,

    POTENTIOMETER_COUNT
    };
    uint16_t pots[POTENTIOMETER_COUNT];

    class Note {
    public:
    byte note; // MIDI note value
    int8_t velocity;
    Oscil<TABLE_SIZE, AUDIO_RATE> oscil;
    ADSR<CONTROL_RATE, CONTROL_RATE> env;
    int8_t current_vol; // (envelope * MIDI velocity)
    uint8_t osc2_mag; // volume of Oscil2 relative to Oscil1, given from 0 (only Oscil1) to 255 (only Oscil2)
    bool isPlaying () { return env.playing (); };

    // Modulated LPF as in the E-Licktronic example.
    // NOTE: Here, I'm using separate LFOs for each note, but there's also a point to be made for keeping all LFOs in sync (i.e. a single global lfo).
    Oscil<512, CONTROL_RATE> lfo; // set up to osciallate the LPF's cutoff
    LowPassFilter lpf;
    uint8_t lpf_cutoff_base;
    bool lfo_active;

    Oscil<TABLE_SIZE, AUDIO_RATE> oscil2;
    };
    Note notes[NOTECOUNT];

    #if defined(FAKE_MIDI_IN)
    #include <EventDelay.h>
    EventDelay noteDelay;
    #endif
    MIDI_CREATE_INSTANCE(HardwareSerial, Serial, MIDI);

    void setup(){
    MIDI.begin();
    MIDI.setHandleNoteOn(MyHandleNoteOn);
    MIDI.setHandleNoteOff(MyHandleNoteOff);
    for (byte i = 0; i < NOTECOUNT; ++i) {
    notes[i].env.setADLevels(200,100);
    notes[i].env.setDecayTime(100);
    notes[i].env.setSustainTime(1000);
    notes[i].note = 0;
    }

    #if FAKE_MIDI
    noteDelay.set (1000);
    #endif
    p
    startMozzi(CONTROL_RATE); // set a control rate of 64 (powers of 2 please)
    }

    const int8_t *potValueToWaveTable (unsigned int value) {
    for (uint8_t i = 0; i < NUM_TABLES-1; ++i) {
    if (value <= (1024 / NUM_TABLES)) return (WAVE_TABLES[i]);
    value -= (1024 / NUM_TABLES);
    }
    return WAVE_TABLES[NUM_TABLES-1];
    }

    void readPots() {
    #if defined(FAKE_POTS)
    // Fake potentiometers: Fill with random values
    for (int i = 0; i < POTENTIOMETER_COUNT; ++i) {
    pots[i] = random (1024);
    }
    pots[LPFCutoffPot] = pots[LPFCutoffPot] >> 1 & (1023); // Lower cutoffs reserved for actual user interaction, as they can turn off sounds, completely.
    pots[LPFResonancePot] = pots[LPFResonancePot] << 1; // Similarly, keep resonance to a safe limit for random parameters
    #else
    // Real potentiometers. Adjust the pot mapping to your liking.
    pots[WaveFormPot] = mozziAnalogRead(A0);
    //pots[OctavePot] = mozziAnalogRead(A1); // Skipped
    pots[AttackPot] = mozziAnalogRead(A2);
    pots[ReleasePot] = mozziAnalogRead(A3);

    /* // Additive Synthesis
    WaveForm2Pot,
    Oscil2OctavePot,
    Oscil2DetunePot,
    Oscil2MagnitudePot, */

    // Modulated LPF
    pots[LPFCutoffPot] = mozziAnalogRead(A4);
    pots[LPFResonancePot] = mozziAnalogRead(A5);
    pots[LFOSpeedPot] = mozziAnalogRead(A6);
    pots[LFOWaveFormPot] = mozziAnalogRead(A7);
    #endif
    }

    // Update parameters of the given notes. Usually either called with a single note, or all notes at once.
    void updateNotes (Note *startnote, uint8_t num_notes) {
    unsigned int attack = map(pots[AttackPot],0,1024,20,2000);
    unsigned int releas = map(pots[ReleasePot],0,1024,40,3000);

    // LPF
    float speed_lfo = map(pots[LFOSpeedPot],0,1024,.1,10);
    uint8_t cutoff=map(pots[LPFCutoffPot],0,1024,20,255);
    uint8_t resonance;
    if (potValueToWaveTable(pots[WaveFormPot])==WAVE_TABLES[1]) resonance=map(pots[LPFResonancePot],0,1024,0,120); // Special casing for sine waves: Use somewhat lower resonance, here
    else resonance=map(pots[LPFResonancePot],0,1024,0,170);

    for (uint8_t i = 0; i < num_notes; ++i) {
    startnote[i].env.setAttackTime(attack); //Set attack time
    startnote[i].env.setReleaseTime(releas);//Set release time
    startnote[i].oscil.setTable (potValueToWaveTable(pots[WaveFormPot]));

    // LPF
    startnote[i].lfo_active = pots[LPFCutoffPot] >= 5; // fully disable LFO on very low frequency
    if (startnote[i].lfo_active) {
    startnote[i].lfo.setTable (potValueToWaveTable(pots[LFOWaveFormPot]));
    startnote[i].lfo.setFreq (speed_lfo);
    }
    startnote[i].lpf.setResonance(resonance);
    startnote[i].lpf_cutoff_base = cutoff;

    /* // Wave mixing
    notes[i].oscil2.setTable (potValueToWaveTable(pots[WaveForm2Pot]));
    notes[i].oscil2.setFreq (mtof (notes[i].note + 28 - (pots[Oscil2OctavePot] >> 8) * 12 - pots[Oscil2DetunePot] >> 5));
    notes[i].osc2_mag = pots[Oscil2MagnitudePot] >> 2; */
    }
    }

    void updateControl(){
    MIDI.read();

    #if defined(FAKE_MIDI_IN)
    if (noteDelay.ready ()) {
    MyHandleNoteOn (1, random (20) + 77, 100);
    noteDelay.start (1000);
    }
    #endif

    #if !defined(FAKE_POTS) // Fake (random!) pots should not update on every control cycle! They fluctuate too much.
    readPots();
    #endif

    // If you enable the line below, here (and disable the corresponding line in MyHandleNoteOn(), notes _already playing_ will be affected by pot settings.
    // Of course, updating more often means more more CPU load. You may have to reduce the NOTECOUNT.
    // updateNotes (notes, NOTECOUNT);

    for (byte i = 0; i < NOTECOUNT; ++i) {
    notes[i].env.update ();
    notes[i].current_vol = notes[i].env.next () * notes[i].velocity >> 8;

    if (notes[i].lfo_active) notes[i].lpf.setCutoffFreq((notes[i].lpf_cutoff_base*(128+notes[i].lfo.next()))>>8);
    else (notes[i].lpf.setCutoffFreq(notes[i].lpf_cutoff_base));
    }
    }

    int updateAudio(){
    int ret = 0;
    for (byte i = 0; i < NOTECOUNT; ++i) {
    // ret += ((long) notes[i].current_vol * ((notes[i].oscil2.next() * notes[i].osc2_mag + notes[i].oscil.next() * (256u - notes[i].osc2_mag)))) >> 14; // Wave mixing
    ret += ((int) notes[i].current_vol * notes[i].lpf.next(notes[i].oscil.next())) >> 10; // LPF
    }
    return ret;
    }

    void loop(){
    audioHook(); // required here
    }

    void MyHandleNoteOn(byte channel, byte pitch, byte velocity) {
    if (velocity > 0) {
    for (byte i = 0; i < NOTECOUNT; ++i) {
    if (!notes[i].isPlaying ()) {
    #if defined(FAKE_POTS)
    readPots();
    #endif
    // Initialize current note with current parameters. Depending on your taste and usecase, you may want to disable this, and enable the corresponding line
    // inside updateControl(), instead.
    updateNotes(&notes[i], 1);

    int f = mtof(pitch);
    notes[i].note = pitch;
    notes[i].oscil.setPhase (0); // Make sure oscil1 and oscil2 start in sync
    notes[i].oscil.setFreq (f);
    notes[i].env.noteOn();
    notes[i].velocity = velocity;

    // LPF
    notes[i].lfo.setPhase (TABLE_SIZE / 4); // 90 degree into table; since the LFO is oscillating _slow_, we cannot afford a random starting point */

    // Wave mixing
    // notes[i].oscil2.setPhase (0);
    break;
    }
    }
    } else {
    MyHandleNoteOff (channel, pitch, velocity);
    }
    }

    void MyHandleNoteOff(byte channel, byte pitch, byte velocity) {
    for (byte i = 0; i < NOTECOUNT; ++i) {
    if (notes[i].note == pitch) {
    if (!notes[i].isPlaying ()) continue;
    notes[i].env.noteOff ();
    notes[i].note = 0;
    //break; Continue the search. We might actually have two instances of the same note playing/decaying at the same time.
    }
    }
    }