Close

Look at that! Is it a Polyphonic Instrument? Not yet :(

A project log for Arduino (ESP32) Standalone Accordion

Polyphonic Piano Accordion made from a cheap Melodica, some buttons and an ESP32 microcontroller

bruno-campidelliBruno Campidelli 08/23/2024 at 01:350 Comments

Hey, we have some sound being triggered now!

Only four notes (C, D, E and F) sampling at the octave 4.

Is it Polyphonic? Hum, sort of? I mean, it plays the four notes at the same time. Is it pleasant to hear? Nope, not at all (yet!).

Here are all the pieces together:

And the sketch that is producing this wonderful sound haha:

#include "MozziConfigValues.h"

#define MOZZI_AUDIO_MODE   MOZZI_OUTPUT_I2S_DAC
#define MOZZI_I2S_PIN_BCK  26
#define MOZZI_I2S_PIN_WS   25
#define MOZZI_I2S_PIN_DATA 22
#define MOZZI_CONTROL_RATE 256

#include <Arduino.h>
#include <Keypad.h>
#include <Mozzi.h>
#include <Oscil.h>
#include <tables/cos8192_int8.h>
#include <mozzi_midi.h>
#include <ADSR.h>

// Envelope controllers
#define ATTACK       100
#define DECAY        200
#define SUSTAIN      500 // Reduced sustain time
#define RELEASE      500 // Reduced release time
#define ATTACK_LEVEL 127 // Lower maximum amplitude level
#define DECAY_LEVEL  127 // Lower decay level

// SETTINGS
#define OCTAVE        4
#define MAX_POLYPHONY LIST_MAX

// Keypad configuration
const byte ROWS = 2;
const byte COLS = 2;
byte key_indexes[ROWS][COLS] = {
  {1, 2},
  {3, 4}
};
byte rowPins[ROWS] = {14, 12};
byte colPins[COLS] = {32, 33};

Keypad keypad = Keypad(makeKeymap(key_indexes), rowPins, colPins, ROWS, COLS);

// Voices
struct Voice {
    Oscil<COS8192_NUM_CELLS, AUDIO_RATE> osc;
    ADSR<MOZZI_CONTROL_RATE, AUDIO_RATE> env;
    byte note;
};

Voice voices[MAX_POLYPHONY];

void noteOff(byte note) {
    for (int i = 0; i < MAX_POLYPHONY; i++) {
        if (voices[i].note == note) {
            voices[i].env.noteOff();
            voices[i].note = 0;
            Serial.print("Note Off: ");
            Serial.println(note);
            return;
        }
    }
}

void noteOn(byte note) {
    int noteIndex = 0;
    for (; noteIndex < MAX_POLYPHONY; noteIndex++) {
        if (!voices[noteIndex].env.playing()) {
            // This voice is not playing, let's use it then
            break;
        }
        if (voices[noteIndex].note == note) {
            // This note is already playing, ignore
            return;
        }
        if (noteIndex + 1 == MAX_POLYPHONY) {
            // This is the last voice and it is occupied,
            // let's steal the oldest voice (index 0) and reuse it
            noteIndex = 0;
            break;
        }
    }
    voices[noteIndex].note = note;
    voices[noteIndex].osc.setFreq(mtof(note));
    voices[noteIndex].env.noteOn();
    Serial.print("Note On: ");
    Serial.println(note);
}

void play() {
    if (keypad.getKeys()) { 
        for (int i = 0; i < LIST_MAX; i++) {
            if (keypad.key[i].stateChanged) {
                byte note = (OCTAVE * 12) + 11 + keypad.key[i].kchar;
                KeyState state = keypad.key[i].kstate;
                if (state == PRESSED) {
                    noteOn(note);
                } else if (state == RELEASED) {
                    noteOff(note);
                }
            }
        }
    }
}

void setup() {
    Serial.begin(115200);

    for (int i = 0; i < MAX_POLYPHONY; i++) {
        voices[i].osc.setTable(COS8192_DATA);
        voices[i].env.setADLevels(ATTACK_LEVEL, DECAY_LEVEL);
        voices[i].env.setTimes(ATTACK, DECAY, SUSTAIN, RELEASE);
    }

    startMozzi(MOZZI_CONTROL_RATE);
}

void updateControl() {
    play();

    for (int i = 0; i < MAX_POLYPHONY; i++) {
        voices[i].env.update();
    }
}

AudioOutput updateAudio() {
    long currentSample = 0;
    for (unsigned int i = 0; i < MAX_POLYPHONY; i++) {
        if (voices[i].env.playing()) {
            currentSample += voices[i].osc.next() * voices[i].env.next();
        }
    }
    return MonoOutput::fromAlmostNBit(20, currentSample);
}

void loop() {
    audioHook();
}

Next step: more buttons! 

Discussions