Progetto sintetizzatore con ARDUINOUNO

Buongiorno ho realizzato il seguente sintetizzatore con ARDUINOUNO e YUN.
Si tratta di un sintetizzatore bifonico con 5 armoniche.
Ci sono quindi 5 oscillatori che generano 5 segnali sinusoidali diversi e fanno capo ad un mixer audio che li somma per fare la sintesi additiva.
Sono riuscito a riprodurre suoni interessanti combinado frequenze e ampiezze audio, ma mi sono piantato nella riproduzione dell'inviluppo.
Riporto di seguito la foto del progetto:


E sotto il sorgente con ARDUINO:

#include "Arduino.h"
#include "MCP4251.h"
#include "TimerOne.h"
#define MAX_BUFFER 2

//void read_USB();

int elemento_buffer=0;
char buffer[MAX_BUFFER];


#define cs1 2
#define cs2 3
#define cs3 4
#define cs4 5
#define cs5 6
#define cs6 7
#define cs7 8
#define cs8 9
#define cs9 10
#define cs14 14

#define pot0ResistanceRmax 100000 // These resistance values may vary
#define pot0ResistanceRmin 0
#define pot1ResistanceRmax 100000
#define pot1ResistanceRmin 0

extern TimerOne Timer0;
extern TimerOne Timer2;
extern TimerOne Timer3;


      float fr0=879;
      int a1=3300;
      float fr1=1783;
      int a2=9900;
      float fr2=2722;
      int a3=17000;
      float fr3=3565;
      int a4=17800;
      float fr4=6528;
      int a5=19000;
      int mast_vol=20000;
      int vol=20000;
      unsigned long tatt = 20000;
      unsigned long microsecatt = (tatt / 1000);
      unsigned long tdec = 2000000000;
      unsigned long microsecdec = (tdec / 1000);


MCP4251 f0(cs1, pot0ResistanceRmax, pot0ResistanceRmin, pot1ResistanceRmax, pot1ResistanceRmin);
MCP4251 f1(cs2, pot0ResistanceRmax, pot0ResistanceRmin, pot1ResistanceRmax, pot1ResistanceRmin);
MCP4251 f2(cs3, pot0ResistanceRmax, pot0ResistanceRmin, pot1ResistanceRmax, pot1ResistanceRmin);
MCP4251 f3(cs4, pot0ResistanceRmax, pot0ResistanceRmin, pot1ResistanceRmax, pot1ResistanceRmin);
MCP4251 f4(cs5, pot0ResistanceRmax, pot0ResistanceRmin, pot1ResistanceRmax, pot1ResistanceRmin);
MCP4251 f5(cs6, pot0ResistanceRmax, pot0ResistanceRmin, pot1ResistanceRmax, pot1ResistanceRmin);
MCP4251 f6(cs7, pot0ResistanceRmax, pot0ResistanceRmin, pot1ResistanceRmax, pot1ResistanceRmin);
MCP4251 f7(cs8, pot0ResistanceRmax, pot0ResistanceRmin, pot1ResistanceRmax, pot1ResistanceRmin);
MCP4251 f8(cs9, pot0ResistanceRmax, pot0ResistanceRmin, pot1ResistanceRmax, pot1ResistanceRmin);
MCP4251 f9(cs14, pot0ResistanceRmax, pot0ResistanceRmin, pot1ResistanceRmax, pot1ResistanceRmin);

void setup() {
    pinMode(15, OUTPUT);
    Serial.begin(9600);
    f0.begin();
    f0.DigitalPotTerminalBConnect(0);
    f0.DigitalPotTerminalAConnect(0);
    f0.DigitalPotWiperConnect(0);
    f1.begin();
    f1.DigitalPotTerminalBConnect(0);
    f1.DigitalPotTerminalAConnect(0);
    f1.DigitalPotWiperConnect(0);
    f2.begin();
    f2.DigitalPotTerminalBConnect(0);
    f2.DigitalPotTerminalAConnect(0);
    f2.DigitalPotWiperConnect(0);
    f3.begin();
    f3.DigitalPotTerminalBConnect(0);
    f3.DigitalPotTerminalAConnect(0);
    f3.DigitalPotWiperConnect(0);
    f4.begin();
    f4.DigitalPotTerminalBConnect(0);
    f4.DigitalPotTerminalAConnect(0);
    f4.DigitalPotWiperConnect(0);
    f5.begin();
    f5.DigitalPotTerminalBConnect(0);
    f5.DigitalPotTerminalAConnect(0);
    f5.DigitalPotWiperConnect(0);
    f6.begin();
    f6.DigitalPotTerminalBConnect(0);
    f6.DigitalPotTerminalAConnect(0);
    f6.DigitalPotWiperConnect(0);
    f7.begin();
    f7.DigitalPotTerminalBConnect(0);
    f7.DigitalPotTerminalAConnect(0);
    f7.DigitalPotWiperConnect(0);
    f8.begin();
    f8.DigitalPotTerminalBConnect(0);
    f8.DigitalPotTerminalAConnect(0);
    f8.DigitalPotWiperConnect(0);
    f9.begin();
    f9.DigitalPotTerminalBConnect(0);
    f9.DigitalPotTerminalAConnect(0);
    f9.DigitalPotWiperConnect(0);
    //Timer0.initialize(1000000000);
    //Timer0.attachInterrupt(read_USB);
    //Timer1.initialize(microsecdec);  
    //Timer1.attachInterrupt(decay);  
    //interrupts();
    Serial.begin(115200);
    while (!Serial)
      delay(10); // will pause Zero, Leonardo, etc until serial console opens

    Serial.println("OK!");
    //Timer1.initialize(1000000);     //testa se ci sono comandi sulla USB ogni 1msec
    //Timer1.attachInterrupt(read_USB); // read USB to run every 0.15 seconds
}

uint16_t wiper0;
uint16_t wiper1;




float conv_freq4_5(float freq)
{
    float restot;
    restot = 1 / (freq * 0.082);
    restot = restot * 1000000;
    restot = restot - 2200;
    restot = restot / 2;
    return restot;
}

float conv_freq1_2_3(float freq)
{
    float restot;
    restot = 1 / (freq * 6.28 * 0.022);
    restot = restot * 1000000;
    return restot;
}

void freq0(float freq)
{
    float resistenza = conv_freq1_2_3(freq);
    //primo pot. (10K)
    wiper0 = f0.DigitalPotResistanceToPosition(0, resistenza * 2);
    f0.DigitalPotSetWiperPosition(0, wiper0);
    //secondo pot. (10K)
    wiper1 = f0.DigitalPotResistanceToPosition(1, resistenza * 2);
    f0.DigitalPotSetWiperPosition(1, wiper1);
    //terzo pot. dig. (5K)
    wiper0 = f1.DigitalPotResistanceToPosition(0, resistenza);
    f1.DigitalPotSetWiperPosition(0, wiper0);
}

void freq1(float freq)
{
    float resistenza = conv_freq1_2_3(freq);
    //primo pot. (10K)
    wiper1 = f2.DigitalPotResistanceToPosition(1, resistenza * 2);
    f2.DigitalPotSetWiperPosition(1, wiper1);
    //secondo pot. (10K)
    wiper0 = f2.DigitalPotResistanceToPosition(0, resistenza * 2);
    f2.DigitalPotSetWiperPosition(0, wiper0);
    //terzo pot. dig. (5K)
    wiper1 = f1.DigitalPotResistanceToPosition(1, resistenza);
    f1.DigitalPotSetWiperPosition(1, wiper1);
}

void freq2(float freq)
{
    float resistenza = conv_freq1_2_3(freq);
    //primo pot. (10K)
    wiper0 = f3.DigitalPotResistanceToPosition(0, resistenza * 2);
    f3.DigitalPotSetWiperPosition(0, wiper0);
    //secondo pot. (10K)
    wiper1 = f3.DigitalPotResistanceToPosition(1, resistenza * 2);
    f3.DigitalPotSetWiperPosition(1, wiper1);
    //terzo pot. dig. (5K)
    wiper1 = f4.DigitalPotResistanceToPosition(1, resistenza);
    f4.DigitalPotSetWiperPosition(1, wiper1);
}

void freq3(float freq)
{
    float resistenza = conv_freq4_5(freq);
    //pot. 1
    wiper0 = f4.DigitalPotResistanceToPosition(0, resistenza);
    f4.DigitalPotSetWiperPosition(0, wiper0);
    //pot. 2
    wiper1 = f5.DigitalPotResistanceToPosition(1, resistenza);
    f5.DigitalPotSetWiperPosition(1, wiper1);
}

void freq4(float freq)
{
    float resistenza = conv_freq4_5(freq);
    //pot. 1
    wiper0 = f5.DigitalPotResistanceToPosition(0, resistenza);
    f5.DigitalPotSetWiperPosition(0, wiper0);
    //pot. 2
    wiper1 = f6.DigitalPotResistanceToPosition(1, resistenza);
    f6.DigitalPotSetWiperPosition(1, wiper1);
}


void amp1(int amp)
{
  //wiper0 f6
  wiper0 = f6.DigitalPotResistanceToPosition(0, amp);
  f6.DigitalPotSetWiperPosition(0, wiper0); 
}

void amp3(int amp)
{
  //wiper1 f7
  wiper1 = f7.DigitalPotResistanceToPosition(1, amp);
  f7.DigitalPotSetWiperPosition(1, wiper1); 
}

void amp2(float amp)
{
  //wiper0 f7
  wiper0 = f7.DigitalPotResistanceToPosition(0, amp);
  f7.DigitalPotSetWiperPosition(0, wiper0); 
}

void amp4(int amp)
{
  //wiper0 f8
  wiper0 = f8.DigitalPotResistanceToPosition(0, amp);
  f8.DigitalPotSetWiperPosition(0, wiper0); 
}

void amp5(int amp)
{
  //wiper1 f8
  wiper1 = f8.DigitalPotResistanceToPosition(1, amp);
  f8.DigitalPotSetWiperPosition(1, wiper1); 
}

void master_vol(int vol)
{
  //wiper0 f9
  wiper0= f9.DigitalPotResistanceToPosition(0, vol);
  f9.DigitalPotSetWiperPosition(0, wiper0); 
  //wiper1 f9
  wiper1= f9.DigitalPotResistanceToPosition(1, vol);
  f9.DigitalPotSetWiperPosition(1, wiper1); 
}

void cut_off(float ft)
{
  
}

void inviluppo(void)
{
  //attacco a gradino
  if(microsecatt == 0)
      {
        freq0(fr0);
        freq1(fr1);
        freq2(fr2);
        freq3(fr3);
        freq4(fr4);
        amp1(a1);
        amp2(a2);
        amp3(a3);
        amp4(a4);
        amp5(a5);
        master_vol(vol);
      }
      //attacco a rampa variabile
      else
      { 
        freq0(fr0);
        freq1(fr1);
        freq2(fr2);
        freq3(fr3);
        freq4(fr4);
        amp1(a1);
        amp2(a2);
        amp3(a3);
        amp4(a4);
        amp5(a5);
        if(vol!=0)
        { 
          master_vol(vol--);
          //delay(microsecatt);
        } 
      }
      //decay 
      /*if(vol>=0 && vol==mast_vol-5000)
      { 
        master_vol(vol++);
        //delay(1);
      }
      //sustan
      
      //relese
      if(vol<=20000)
      { 
        master_vol(vol++);
        delay(1);
      }
      if(vol>=20000)      vol=20000;*/
}


void loop() {
    if((Serial.available())>0)  
    {      
      // se c'è qualche byte da leggere, allora...
      char carattere=Serial.read();      // ...leggi byte
      //if(carattere=='\r') continue;
      if(carattere=='\n')
      {
        buffer[elemento_buffer]=0;       // inserisco un carettere NULL per indicare la fine dell'array
        elemento_buffer=0;               // resetto l'indice dell'array       
      }
      if(carattere == 'a') 
      {
        digitalWrite(15, HIGH);
        inviluppo();
      }
      Serial.println(buffer); 
      buffer[elemento_buffer]=carattere; // creo l'array di caratteri
      elemento_buffer++;                 // incremento l'indice del buffer
      if(elemento_buffer==MAX_BUFFER)    // se si raggiunge il limite di byte del buffer allora...
      {
        elemento_buffer=MAX_BUFFER-1;    // ...sovrascrivi quello precedente
      } 
      buffer[0]='0';    //pulisci buffer
      digitalWrite(15, LOW);
    }
    else 
    {
      vol=20000;
      master_vol(vol);
    }
}

Non riesco in particolare a far si che ad ogni pressione del tasto 'a' venga riprodotto un ATTACK graduale fino la volume massimo, ma si sente un ATTACK a volume a tratti poco gradevole da sentire.
Ho provato a generare le 4 fasi dell'inviluppo con l'interruprt, ma si
sente un effetto sgradevolissimo nn voluto, dovuto alla continua commutazione dal programma principale alla routine di interrupt.
Ho optato quindi per la soluzione senza interrupt da timer; con la chiamata inviluppo() da loop{} ad ogni presisone della lettera 'a'.
L'effetto però non è il massimo dato che non riesco a variare gradualmente il tempo di inviluppo dell'ATTACK agendo nell'incremento o decremento della variabile vol.
Non riesco ad ottenere una variazione graduale e non a tratti del volume del progredire dell'ATTACK.
Nessuno mi puo' aiutare con qualche idea?
Riporto pure il codice python che invia la lettera 'a' all'ARDUINO:

import time
import serial
import keyboard
#import pynput



def invia():
    ser.write(i)            
    

try:    
    ser = serial.Serial("/dev/ttyACM0", 115200)
    
    if ser.is_open:
       print ("\n Il collegamento è stato effettuato con successo  \n")

    #ser.close()

except:
    print("""
    Collegamento fallito,
    controllare se il dispositivo è stato connesso correttamente,
    o di avere inserito il nome della porta corretto \n""")


i=b'a\n'


while True:  # making a loop
        if keyboard.is_pressed('a'):  # if key 'a' is pressed 
            print('You Pressed "a" Key!')
            ser.write(i)
        else:
            print("\nTasto non premuto")# if user pressed a key other than the given key the loop will break


Chiedo quindi aiuto e consigli per ottenere un invìiluppo graduale e più piacevole da sentire: non a tratti.

ciao

al momento non ho suggerimenti sul codice anche perchè ci sono cose che non mi sono chiare (ad esempio dove usi la funzione inviluppo?)...hai provato a fare delle simulazioni?...cioè...stampi su seriale anzichè modulare fisicamente?...per un progetto del genere vedo necessario un'oscilloscopio ed un tester decenti...spero tu ne sia munito.

Si ho tester ed oscilloscopio decente della RIGOL.
Ho pensato di inviare solo comandi lato python mantre il grosso della modulazione la fa ARDUINOUNO.
La modulazione dlel'inviluppo la fa ARDUINO via software con la chiamata master_vol(vol--) la funzione inviluppo() è dentro all' if(carattere=='a') {} etc. etc.. come vedi nel codice.
Quindi ad ogni pressione del carattere 'a' viene suonata la nota con lo specifico ATTACK dentro la funzione inviluppo (secondo if() della funzione inviluppo in questo caso che permette un ATTACH lento).
Al momento non riesco a regolare la velocità di propagazione dell'ATTACK in modo graduale e non a scatti progressivi.
Ho fatto simulazioni dell'ATTACK con master_vol(vol--) dentro la funzione inviluppo().
Ti posso inviare delle tracce audio e tracce oscilloscopio sullo stato dll'arte verso sera.

mi era sfuggito....

ok...giusto per capire che effetto ottieni...

poi uno schema del tutto sarebbe utile...e per me anche una descrizione di quello che dovrebbe accadere "fisicamente"...immagino un incremento con rampa variabile di una tensione/corrente.
risolto l'attack il resto (sustain, decay, release) dovrebbe essere relativamente facile...

QUESTO sembra interessante...se ne parlava in un vecchio thread qui nel forum...non ho verificato ma sembra che codice e schema siano pubblici...potrebbe essere d'aiuto....

Ho dato un'occhiata al codice ARDUINOUNO e sembra molto complesso, molto più complesso del mio.
Il mio schema elettrico è basilare, ma l'obbiettivo è ottenere effetti simili a quello del link con il software.
Posso inviare lo schema in formato per Eagle? Lo sachema è complesso dato che il dispositivo è bifonico quindi se dovessi rifarlo per fidocad mi ci vorrebe parecchio tempo, quindi per favore lasciatemi tenerlo per Eagle data la sua complessità.
schema.zip (782.9 KB)
Sopra lo schema da scompattare ed è in formato .sch da usare con l 'applicazione Eagle disponibile sia per linux che per win.

Nturalmente SI ... e, volendo, da Eagle puoi anche esportarlo in pdf (o jpg) e metterelo qui ... giusto per chi vuole dargli un'occhiata senza avere Eagle :wink:

Guglielmo

Ecco lo schema in jpg: deve essere ingrandito parecchio per vedere i dettagli. I potenziomentri cerchiati sono tutti digitali fatti con MCP4251. Nella scheda figlia verticale ce ne sono 20 quindi 10 integrati MCP4251.

Ecco sotto la traccia dell'oscilloscopio dell'effetto ottenuto con quel codice:
Voce 002_sd.m4a.zip (488.5 KB)

Ed infine l'audio con volume al massimo, quindi con ATTACK arrivato al massimo:
NewFile1.stp.zip (685 Bytes)
NewFile2.stp.zip (692 Bytes)

Spero che il formato audio sopra sia leggibile sotto win.

il file audio lo riproduco...i due .stp non so come gestirli...
se non ho capito male dall'audio...il suono inizia circa al 13-14mo secondo...poi ha due micro scalini al 18mo ed uno più intenso al 19mo ed al 20mo un salto grande (volume massimo?) poi il sustain è un po' "ondulante"... ma forse toccavi i vari fili...l'andamento è questo?...quindi non è fluido e va a scalini ?

Esatto va a scalini. 20esimo secondo un salto grande e non ho toccato i fili ma ho tolto la cuffia dal cell. I file sono registrati col cell.
Comunque l'ATTACK va a scalini.
Che ne dici dello schema?

Per avere 12V in uscita, il trimmer R32 da 10k deve essere regolato a circa 2kOhm e per i 5V R33 deve essere regolato a circa 700 Ohm. Se fanno falso contatto, la tensione in uscita salta a circa 15V!...
Un condensatore in parallelo a ciascun trimmer, poi, ci starebbe bene per ridurre il rumore.
Anche un condensatore prima di ciascun regolatore non ci starebbe male.
Spero che tu abbia messo un po' di 100nF sulle alimentazioni sparsi per il circuito.

per lo schema come concetti di elettronica...ci sono altri qui nel forum che possono darti suggerimenti (Vedi Datmar)...io al massimo faccio il caffè.

per lo schema come "disegno" solitamente tendo a dividere in cose "piccole" una singola cosa "grande"...mi è più semplice saltare da una pagina all'altra piuttosto che scorrere su e giù.

per gli scalini...provo a dare una occhiata al programma...ti faccio sapere.

Dipende... Certi schemi spezzettati diventano impossibili da seguire!

Ciao Avete trovato qualche soluzione ideale?

ciao...eh...come sempre mille impegni ...
ho (ri)dato una occhiata al codice...e continuo ad avere dei dubbi:
riusciresti a postare uno schema logico di come verresti funzionasse il tutto!?
poi...come o cosa scandisce il tempo per la rampa dell'attacco?

se creando una variabile vedi che questa assume un colore NON "nero" vuol dire che è già in uso da altre parti...ti consiglio di usare "nomi" unici...vedi buffer nel tuo programma.

Eccomi con un diagramma di flusso del funzionamento del tutto.
Scusate il ritardo della risposta (altri mille impegni).
flow-chart.pdf (884.2 KB)

Come vedete, prima della gestione dell'inviluppo c'è un if che rileva la pressione della lettera "a" sulla tastiera.
Tale pressione determina se la scheda deve "suonare" o no.
Se ci sono dubbi sul flow-chart ditemi.

prova questo e dimmi se rimane stabile:

#include "Arduino.h"
#include "MCP4251.h"
#include "TimerOne.h"
#define MAX_BUFFER 2

//void read_USB();

int elemento_buffer = 0;
char buffer[MAX_BUFFER];


#define cs1 2
#define cs2 3
#define cs3 4
#define cs4 5
#define cs5 6
#define cs6 7
#define cs7 8
#define cs8 9
#define cs9 10
#define cs14 14

#define pot0ResistanceRmax 100000 // These resistance values may vary
#define pot0ResistanceRmin 0
#define pot1ResistanceRmax 100000
#define pot1ResistanceRmin 0

extern TimerOne Timer0;
extern TimerOne Timer2;
extern TimerOne Timer3;


float fr0 = 879;
int a1 = 3300;
float fr1 = 1783;
int a2 = 9900;
float fr2 = 2722;
int a3 = 17000;
float fr3 = 3565;
int a4 = 17800;
float fr4 = 6528;
int a5 = 19000;
int mast_vol = 20000;
int vol = 20000;
unsigned long tatt = 20000;
//unsigned long microsecatt = (tatt / 1000);
unsigned long microsecatt = 0;
unsigned long tdec = 2000000000;
unsigned long microsecdec = (tdec / 1000);


MCP4251 f0(cs1, pot0ResistanceRmax, pot0ResistanceRmin, pot1ResistanceRmax, pot1ResistanceRmin);
MCP4251 f1(cs2, pot0ResistanceRmax, pot0ResistanceRmin, pot1ResistanceRmax, pot1ResistanceRmin);
MCP4251 f2(cs3, pot0ResistanceRmax, pot0ResistanceRmin, pot1ResistanceRmax, pot1ResistanceRmin);
MCP4251 f3(cs4, pot0ResistanceRmax, pot0ResistanceRmin, pot1ResistanceRmax, pot1ResistanceRmin);
MCP4251 f4(cs5, pot0ResistanceRmax, pot0ResistanceRmin, pot1ResistanceRmax, pot1ResistanceRmin);
MCP4251 f5(cs6, pot0ResistanceRmax, pot0ResistanceRmin, pot1ResistanceRmax, pot1ResistanceRmin);
MCP4251 f6(cs7, pot0ResistanceRmax, pot0ResistanceRmin, pot1ResistanceRmax, pot1ResistanceRmin);
MCP4251 f7(cs8, pot0ResistanceRmax, pot0ResistanceRmin, pot1ResistanceRmax, pot1ResistanceRmin);
MCP4251 f8(cs9, pot0ResistanceRmax, pot0ResistanceRmin, pot1ResistanceRmax, pot1ResistanceRmin);
MCP4251 f9(cs14, pot0ResistanceRmax, pot0ResistanceRmin, pot1ResistanceRmax, pot1ResistanceRmin);

void setup() {
  pinMode(15, OUTPUT);
  Serial.begin(9600);
  f0.begin();
  f0.DigitalPotTerminalBConnect(0);
  f0.DigitalPotTerminalAConnect(0);
  f0.DigitalPotWiperConnect(0);
  f1.begin();
  f1.DigitalPotTerminalBConnect(0);
  f1.DigitalPotTerminalAConnect(0);
  f1.DigitalPotWiperConnect(0);
  f2.begin();
  f2.DigitalPotTerminalBConnect(0);
  f2.DigitalPotTerminalAConnect(0);
  f2.DigitalPotWiperConnect(0);
  f3.begin();
  f3.DigitalPotTerminalBConnect(0);
  f3.DigitalPotTerminalAConnect(0);
  f3.DigitalPotWiperConnect(0);
  f4.begin();
  f4.DigitalPotTerminalBConnect(0);
  f4.DigitalPotTerminalAConnect(0);
  f4.DigitalPotWiperConnect(0);
  f5.begin();
  f5.DigitalPotTerminalBConnect(0);
  f5.DigitalPotTerminalAConnect(0);
  f5.DigitalPotWiperConnect(0);
  f6.begin();
  f6.DigitalPotTerminalBConnect(0);
  f6.DigitalPotTerminalAConnect(0);
  f6.DigitalPotWiperConnect(0);
  f7.begin();
  f7.DigitalPotTerminalBConnect(0);
  f7.DigitalPotTerminalAConnect(0);
  f7.DigitalPotWiperConnect(0);
  f8.begin();
  f8.DigitalPotTerminalBConnect(0);
  f8.DigitalPotTerminalAConnect(0);
  f8.DigitalPotWiperConnect(0);
  f9.begin();
  f9.DigitalPotTerminalBConnect(0);
  f9.DigitalPotTerminalAConnect(0);
  f9.DigitalPotWiperConnect(0);
  //Timer0.initialize(1000000000);
  //Timer0.attachInterrupt(read_USB);
  //Timer1.initialize(microsecdec);
  //Timer1.attachInterrupt(decay);
  //interrupts();
  Serial.begin(115200);
  while (!Serial)
    delay(10); // will pause Zero, Leonardo, etc until serial console opens

  Serial.println("OK!");
  //Timer1.initialize(1000000);     //testa se ci sono comandi sulla USB ogni 1msec
  //Timer1.attachInterrupt(read_USB); // read USB to run every 0.15 seconds
}

uint16_t wiper0;
uint16_t wiper1;




float conv_freq4_5(float freq)
{
  float restot;
  restot = 1 / (freq * 0.082);
  restot = restot * 1000000;
  restot = restot - 2200;
  restot = restot / 2;
  return restot;
}

float conv_freq1_2_3(float freq)
{
  float restot;
  restot = 1 / (freq * 6.28 * 0.022);
  restot = restot * 1000000;
  return restot;
}

void freq0(float freq)
{
  float resistenza = conv_freq1_2_3(freq);
  //primo pot. (10K)
  wiper0 = f0.DigitalPotResistanceToPosition(0, resistenza * 2);
  f0.DigitalPotSetWiperPosition(0, wiper0);
  //secondo pot. (10K)
  wiper1 = f0.DigitalPotResistanceToPosition(1, resistenza * 2);
  f0.DigitalPotSetWiperPosition(1, wiper1);
  //terzo pot. dig. (5K)
  wiper0 = f1.DigitalPotResistanceToPosition(0, resistenza);
  f1.DigitalPotSetWiperPosition(0, wiper0);
}

void freq1(float freq)
{
  float resistenza = conv_freq1_2_3(freq);
  //primo pot. (10K)
  wiper1 = f2.DigitalPotResistanceToPosition(1, resistenza * 2);
  f2.DigitalPotSetWiperPosition(1, wiper1);
  //secondo pot. (10K)
  wiper0 = f2.DigitalPotResistanceToPosition(0, resistenza * 2);
  f2.DigitalPotSetWiperPosition(0, wiper0);
  //terzo pot. dig. (5K)
  wiper1 = f1.DigitalPotResistanceToPosition(1, resistenza);
  f1.DigitalPotSetWiperPosition(1, wiper1);
}

void freq2(float freq)
{
  float resistenza = conv_freq1_2_3(freq);
  //primo pot. (10K)
  wiper0 = f3.DigitalPotResistanceToPosition(0, resistenza * 2);
  f3.DigitalPotSetWiperPosition(0, wiper0);
  //secondo pot. (10K)
  wiper1 = f3.DigitalPotResistanceToPosition(1, resistenza * 2);
  f3.DigitalPotSetWiperPosition(1, wiper1);
  //terzo pot. dig. (5K)
  wiper1 = f4.DigitalPotResistanceToPosition(1, resistenza);
  f4.DigitalPotSetWiperPosition(1, wiper1);
}

void freq3(float freq)
{
  float resistenza = conv_freq4_5(freq);
  //pot. 1
  wiper0 = f4.DigitalPotResistanceToPosition(0, resistenza);
  f4.DigitalPotSetWiperPosition(0, wiper0);
  //pot. 2
  wiper1 = f5.DigitalPotResistanceToPosition(1, resistenza);
  f5.DigitalPotSetWiperPosition(1, wiper1);
}

void freq4(float freq)
{
  float resistenza = conv_freq4_5(freq);
  //pot. 1
  wiper0 = f5.DigitalPotResistanceToPosition(0, resistenza);
  f5.DigitalPotSetWiperPosition(0, wiper0);
  //pot. 2
  wiper1 = f6.DigitalPotResistanceToPosition(1, resistenza);
  f6.DigitalPotSetWiperPosition(1, wiper1);
}


void amp1(int amp)
{
  //wiper0 f6
  wiper0 = f6.DigitalPotResistanceToPosition(0, amp);
  f6.DigitalPotSetWiperPosition(0, wiper0);
}

void amp3(int amp)
{
  //wiper1 f7
  wiper1 = f7.DigitalPotResistanceToPosition(1, amp);
  f7.DigitalPotSetWiperPosition(1, wiper1);
}

void amp2(float amp)
{
  //wiper0 f7
  wiper0 = f7.DigitalPotResistanceToPosition(0, amp);
  f7.DigitalPotSetWiperPosition(0, wiper0);
}

void amp4(int amp)
{
  //wiper0 f8
  wiper0 = f8.DigitalPotResistanceToPosition(0, amp);
  f8.DigitalPotSetWiperPosition(0, wiper0);
}

void amp5(int amp)
{
  //wiper1 f8
  wiper1 = f8.DigitalPotResistanceToPosition(1, amp);
  f8.DigitalPotSetWiperPosition(1, wiper1);
}

void master_vol(int vol)
{
  //wiper0 f9
  wiper0 = f9.DigitalPotResistanceToPosition(0, vol);
  f9.DigitalPotSetWiperPosition(0, wiper0);
  //wiper1 f9
  wiper1 = f9.DigitalPotResistanceToPosition(1, vol);
  f9.DigitalPotSetWiperPosition(1, wiper1);
}

void cut_off(float ft)
{

}

void inviluppo(void)
{
  //attacco a gradino
  if (microsecatt == 0)
  {
    freq0(fr0);
    freq1(fr1);
    freq2(fr2);
    freq3(fr3);
    freq4(fr4);
    amp1(a1);
    amp2(a2);
    amp3(a3);
    amp4(a4);
    amp5(a5);
    master_vol(vol);
  }
  //attacco a rampa variabile
  else
  {
    freq0(fr0);
    freq1(fr1);
    freq2(fr2);
    freq3(fr3);
    freq4(fr4);
    amp1(a1);
    amp2(a2);
    amp3(a3);
    amp4(a4);
    amp5(a5);
    if (vol != 0)
    {
      master_vol(vol--);
      //delay(microsecatt);
    }
  }
  //decay
  /*if(vol>=0 && vol==mast_vol-5000)
    {
    master_vol(vol++);
    //delay(1);
    }
    //sustan

    //relese
    if(vol<=20000)
    {
    master_vol(vol++);
    delay(1);
    }
    if(vol>=20000)      vol=20000;*/
}


void loop() {
  if ((Serial.available()) > 0)
  {
    // se c'è qualche byte da leggere, allora...
    char carattere = Serial.read();    // ...leggi byte
    //if(carattere=='\r') continue;
    if (carattere == '\n')
    {
      buffer[elemento_buffer] = 0;     // inserisco un carettere NULL per indicare la fine dell'array
      elemento_buffer = 0;             // resetto l'indice dell'array
    }
    if (carattere == 'a')
    {
      digitalWrite(15, HIGH);
      inviluppo();
    }
    Serial.println(buffer);
    buffer[elemento_buffer] = carattere; // creo l'array di caratteri
    elemento_buffer++;                 // incremento l'indice del buffer
    if (elemento_buffer == MAX_BUFFER) // se si raggiunge il limite di byte del buffer allora...
    {
      elemento_buffer = MAX_BUFFER - 1; // ...sovrascrivi quello precedente
    }
    buffer[0] = '0';  //pulisci buffer
    digitalWrite(15, LOW);
  }
  else
  {
    //vol = 20000;
   // master_vol(vol);
  }
}

Allora ho copiato il codice nell' IDE e si sente un suono via via
crescente in intensità.
Il codice mi sembra stabile.
Il mio problema non è la stabilità ma creare una variabilità del tempo di inviluppo nell'attacco senza sentire gli scatti a gradino.
Che ne pensi?
DEvo trovare il modo di variare l'attacco a rampa con temporizzazioni diverse (più lente o più veloci).

Ho modificato come segue, così facendo ottengo un suono con attacco a gradino e release graduale a scatti.
Il problema è che non voglio che avvenga un release a scatti: come posso fare? Se cambio vol=vol+0.1 in vol=vol+0.01 o meno si nota
ancora di più il release a scatti.
Ho provato pure a mettere un delay(1); dentro al ciclo for() ma l'effetto a scatti persiste anzi si accentua.
Aiutatemi!

void inviluppo(void)
{
  //attacco a gradino
  //if (microsecatt == 0)
  //{
    freq0(fr0);
    freq1(fr1);
    freq2(fr2);
    freq3(fr3);
    freq4(fr4);
    amp1(a1);
    amp2(a2);
    amp3(a3);
    amp4(a4);
    amp5(a5);
    if(vol==0)
    {
      for(vol=0;vol<=20000;vol=vol+0.1)
      {    
          master_vol(vol);
      }
    }
    if(vol==20000)
    {
      for(vol=20000;vol>0;vol--)
      {
          master_vol(vol);
      }
    }
}


void loop() {
  vol=0;
  inviluppo();
}

Molte cose non le capisco

O meglio, non ne capisco la logica, che mi risulta aliena

Però dopo uno sguardo rapido alla documentazione della libreria risulta che
I potenziometri sono da 256 passi, dividere in centesimi i già tanti 20000 passi di for è quantomeno strambo
La resistenza utile dei potenziometr va da 0 e 100k
Il for parte da 0 per arrivare a 20k
E quindi i passi utili del potenziometro sono si e no una cinquantina

Perché fare tutto questo teatro ?
Questo io mi domando nell'intimo