pschatzmann / arduino-audio-tools

Arduino Audio Tools (a powerful Audio library not only for Arduino)
GNU General Public License v3.0
1.51k stars 233 forks source link

Can't achieve polyphony using multiple instruments and a voicer #1680

Closed campidelli closed 2 months ago

campidelli commented 2 months ago

Problem Description

Hello Mr. Schatzmann, sorry to bother you, but I am trying to create a simple standalone instrument that is triggered by a keypad.

You can check my code https://github.com/campidelli/arduino-accordion if you want to see the complete implementation.

I am probably using it wrongly, could you please give me some advice?

Here is a footage of the issue happening: https://www.youtube.com/watch?v=qL75XVQ7Rl0

Device Description

ESP32 + PCM1502 and a PAM8403 amplifier.

Sketch

#include <Arduino.h>
#include "Keyboard.h"
#include "AudioTools.h"
#include "StkAll.h"

// Define constants
const float AMPLITUDE = 64.0;
const int GROUP = 0;

// Create instances of the audio components
I2SStream i2s;
ArdStreamOut output(i2s, 1);
Clarinet clarinet(440); // Create a clarinet instance
Voicer voicer;
Keyboard keyboard;

// Define key press and release handlers
int getNote(int key) {
    return key + 60;
}

void noteOn(int key) {
    int note = getNote(key);

    Serial.print("Note ");
    Serial.print(note);
    Serial.print(" ON at voice ");
    Serial.println(key);

    voicer.noteOn(note, AMPLITUDE, key);
}

void noteOff(int key) {
    int note = getNote(key);

    Serial.print("Note ");
    Serial.print(note);
    Serial.print(" OFF at voice ");
    Serial.println(key);
    voicer.noteOff(note, AMPLITUDE, key);
}

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

    // Initialize the keyboard
    keyboard.begin();
    keyboard.onKeyPress(noteOn);
    keyboard.onKeyRelease(noteOff);

    // Add the clarinets to the voicer
    for (int i = 0; i < keyboard.getTotalKeys(); i++) {
        voicer.addInstrument(&clarinet, i);
    }

    // Configure the audio output
    auto cfg = i2s.defaultConfig(TX_MODE);
    cfg.bits_per_sample = 16;
    cfg.sample_rate = Stk::sampleRate();
    cfg.channels = 1;
    cfg.pin_bck = 26;
    cfg.pin_ws = 25;
    cfg.pin_data = 22;
    i2s.begin(cfg);
}

void loop() {
    // Update keyboard and process audio
    keyboard.update();
    output.tick(voicer.tick());
}

Other Steps to Reproduce

No response

What is your development environment

PlatformIO

I have checked existing issues, discussions and online documentation

pschatzmann commented 2 months ago

I bet your Keyboard class is the source of trouble and adds to much delay: you call it way too often..

Have a look at this example. If you dont have an AudioKit you can replace it with I2SStream and for the actions you can use the AudioActions class.

campidelli commented 2 months ago

Hi there, thanks for the prompt response.

I had a look at the example and what I got from it is that AudioActions would be ideal if I had one key per pin (or one pin per key), but in my case, it has to be a matrix because I intend to have 72 keys on the bass plus 32 on the treble (it is an accordion).

How could I circumvent that? How would you implement an e-piano for example?

Thanks!

pschatzmann commented 2 months ago

Just have one more look at the sketch how I process e.g. 1k of audio samples for each input from the keyboard. You need to experiment a bit to find your sweet spot...

campidelli commented 2 months ago

Hello Mr. Schatzmann, good news! I combined your approach of processing 1k audio samples and created a task pinned to core 0 to process the keyboard input, it works!

Now, what if I want to write my own instrument? The goal is to create an accordion, like this https://www.youtube.com/watch?v=3mif1_ttTgk

The options are:

Anyway, I am pretty noob in C++, Synths, microcontrollers, etc. But I will get there. Thanks for your help and here is the sketch I have that is working with the Clarinet.h:

#include <Arduino.h>
#include "Keyboard.h"
#include "AudioTools.h"
#include "StkAll.h"

// Define constants
const float AMPLITUDE = 16.0;
const int GROUP = 0;

// Create instances of the audio components
I2SStream i2s;
ArdStreamOut output(i2s, 1);
Clarinet clarinet(440); // Create a clarinet instance
Voicer voicer;
Keyboard keyboard;

// Define key press and release handlers
int getNote(int key) {
    return key + 60;
}

void noteOn(int key) {
    int note = getNote(key);
    voicer.noteOn(note, AMPLITUDE, GROUP);
}

void noteOff(int key) {
    int note = getNote(key);
    voicer.noteOff(note, AMPLITUDE, GROUP);
}

void keyboardTask(void *pvParameters) {
    while (true) {
        keyboard.update();
        vTaskDelay(10 / portTICK_PERIOD_MS);
    }
}

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

    // Add the clarinet to the voicer
    voicer.addInstrument(&clarinet, GROUP);

    // Configure the audio output
    auto cfg = i2s.defaultConfig(TX_MODE);
    cfg.bits_per_sample = 16;
    cfg.sample_rate = Stk::sampleRate();
    cfg.channels = 1;
    cfg.pin_bck = 26;
    cfg.pin_ws = 25;
    cfg.pin_data = 22;
    i2s.begin(cfg);

    // Initialize the keyboard
    keyboard.begin();
    keyboard.onKeyPress(noteOn);
    keyboard.onKeyRelease(noteOff);

    // Pin the keyboard scan task to a core 0
    xTaskCreatePinnedToCore(keyboardTask, "Keyboard Task", 4096, NULL, 1, NULL, 0);
}

void loop() {
  for (int i = 0; i < 1024; i++) {
    output.tick(voicer.tick());
  }
}
pschatzmann commented 2 months ago

You can consider the audio tools library as the preferred output library for the STK framework: So, STK is not deprecated and it is still interesting to use the existing instruments and provided potentially new instruments.

Currently the AudioTools does not provide any comb filter, so feel free to provide some additional functionality via a pull request.