sexta-feira, 6 de julho de 2018

Fentuino - sintetizador com Arduino

 Fentuino 





O que é:




Uma colaboração dos usuários do fórum cifra clube (e quem mais se interessar) para desenvolver um sintetizador (musical) baseado na plataforma Arduino.


Não tenho nenhuma relação com nenhuma marca ou fabricante de nada do que for citado aqui.



Características do projeto:




  • Tem que ser acessível a todos interessados ($);
  • Tem que utilizar componentes que possam ser adquiridos com facilidade;
  • Tem que ser um projeto sem dificuldades de calibração, e que possa ser replicado facilmente;
  • Todo o hardware e software será amplamente divulgado, só usará bibliotecas de software livres;
  • Não envolve ganho monetário.



Essa é uma iniciativa dos usuários do FCC (Fórum Cifra Clube), e conta com a participação de todos, mesmo que não tenha como participar tecnicamente, participe com ideias.


Inicialmente penso em um sintetizador monofônico monotimbral, com possibilidade de ser expandido para polifônico. Vamos discutindo aqui os conceitos e ideias:



Qualquer Arduino resolve. Eu sugiro o uso dos mais econômicos:

  • Arduino uno (15 reais no ebay)
  • Arduino nano (10 reais no ebay)

Existem versões nacionais:  robocore:
https://www.robocore.net/


Tem uma versão de uno a 85 reais e o pro-mini a 29 reais. Esse pro-mini é o nano sem programador. Precisa de um uno ou deum programador para usar o pro-mini.





Mas afinal, o que é um Arduino?



https://www.arduino.cc/



É uma plataforma que consiste em uma placa de circuito impresso com um microprocessador 
e os componentes necessários para ele funcionar e ser programado. Temos acesso aos pinos do microcontrolador, que podem ser configurados como entrada, saída, comunicação, leitura de sensores diversos (no nosso caso a leitura da posição de potenciômetros, por exemplo, é fundamental) etc.






A plataforma tem muito código conveniente já escrito para facilitar sua implementação. Principalmente bibliotecas.


O que são bibliotecas?
São coleções de programas que podem ser adicionados ao ambiente de programação, e adicionam novas funcionalidades para o microcontrolador. Por exemplo, MIDI, que vamos usar.
Bibliotecas são grandes facilitadores na programação, e são provavelmente blocos de programação muito mais estáveis que escrever o código "a partir do zero" toda vez.





Ambiente de programação.







O fabricante fornece um sistema de programação em linguagem C++ muito amigável e claro, o Arduino IDE:

https://www.arduino.cc/en/Main/Software


O fabricante disponibiliza uma referência interessante para ser consultada:


https://www.arduino.cc/reference/en/


Abaixo, uma bela introdução, para quem está tendo o primeiro contato com a plataforma:


https://www.circuitar.com.br/tutoriais/programacao-para-arduino-primei ros-passos/


Arduino MIDI Library v4.3.1:


https://github.com/FortySevenEffects/arduino_midi_library/releases
https://github.com/FortySevenEffects/arduino_midi_library
http://fortyseveneffects.github.io/arduino_midi_library/index.html




O mínimo que você precisa saber sobre MIDI





MIDI (Musical Instrument Digital Interface) é um protocolo de comunicação serial com foco em troca de informações para equipamentos musicais. Ele envia dados e comandos de controle. É um protocolo bastante robusto, utilizado a décadas. Funciona.




O cabo envia mensagen sem uma taxa de 31,25 kbit/s em um cabo geralmente com dois ou três condutores . Na verdade existem vários tipos de cabo MIDI, mas o que nos interessa é o de dois condutores. 








As mensagens são enviadas de 8 bits (= 1 byte) em uma taxa de 31,25 kbit/s. Uma mensagem MIDI típica tem essa cara:


STATUS byte + DATA bytes


Um STATUS byte sempre começa com 1, em binário:


0b1xxxxxxx


Um DATA byte sempre começa com 0, em binário:


0b0xxxxxxx


Notas musicais:


Quando se aperta uma tecla, o que sai é um note on:
Status byte : 1001 CCCC
Data byte 1 : 0PPP PPPP
Data byte 2 : 0VVV VVVV
onde
"CCCC" é o canal MIDI (0 até 15)
"PPP PPPP" é a nota (0 até 127)
"VVV VVVV" é a velocidade (intensidade) ( 0 até 127)


Quando se solta a tecla - note off:
Status byte : 1000 CCCC
Data byte 1 : 0PPP PPPP
Data byte 2 : 0VVV VVVV
onde
"CCCC" é o canal MIDI (0 até 15)
"PPP PPPP" é a nota (0 até 127)
"VVV VVVV" é a velocidade de desligamento ( 0 até 127)


Alguns comandos comuns:
program change
Status byte : 1100 CCCC
Data byte 1 : 0XXX XXXX
Controles MIDI
Status byte : 1011 CCCC
Data byte 1 : 0NNN NNNN
Data byte 2 : 0VVV VVVV
CCCC = canal MIDI
NNNNNNN = número do controle (0 até 127)
VVVVVVV = valor (0 até 127)
por exemplo:
7 = Volume level of the instrument

Canal = 8
Volume = 7
Valor do volume = 127
Status byte : 1011 1000
Data byte 1 : 0000 0111
Data byte 2 : 0111 1111
Esse é o absolutamente básico.
Vamos usar essencialmente NOTE on/off,
bend, modulation e alguns comandos CC.


Quem quiser saber mais detalhes:


http://www.music-software-development.com/midi-tutorial.html




O padrão é usar um acoplador óptico entre o cabo e o Arduino. Isso traz vários benefícios:

  1. Evita problemas de aterramento
  1. Evita acidentes elétricos
  1. Evita ruídos indesejados
6N138 (acoplador óptico)





A cara da conexão é assim 
(colocar uma saída MIDI Thru é sempre bacana):


MIDI IN



Acopladores 6N138 ou 6N139 são excelentes, confiáveis e rápidos. Mas esse não é um componente tão crítico. Existem muitas opções:



Acopladores populares



1) Cada um monta o seu:

Esquema da entrada MIDI para o arduino



MIDI Shield típico.




Quem quiser se aventurar e montar o acoplador em formato de shield:

http://www.instructables.com/id/Arduino-MIDI-in-shield/




O sistema mínimo é esse:

Sistema mínimo.

É necessária uma biblioteca de síntese, e essa é minha sugestão:


http://sensorium.github.io/Mozzi/




Sintetizador Monofônico




Chega o primeiro ponto. Se colocar o protocolo MIDI e o Mozzi no mesmo arduino, será difícil expandir. Se não existe planos de expandir no futuro, um arduino somente resolve.
A conexão entre os arduinos é feita serialmente com protocolo MIDI. 

Sintetizador Polifônico



Vou colocando nesse blog informações para quem quiser seguir o projeto.


Arduino mestre
Pino 0 - RX (entrada MIDI)

Pino 1 - TX (saída MIDI)

Arduino
(s) escravo(s)

Pino 0 - RX (entrada serial)



Links interessantes para quem está interessado no projeto:


http://linksprite.com/wiki/index.php5?title=MIDI_Shield_for_Arduino


https://www.farnell.com/datasheets/1682209.pdf


http://www.farnell.com/datasheets/1682238.pdf




Abaixo o Mega, para quem quiser colocar display e/ou outras firulas:


http://www.farnell.com/datasheets/810077.pdf





uma nota, coloca-se a prioridade em algo, por exemplo, a nota mais alta, a nota mais baixa, a
última tecla pressionada etc...


Já em um esquema polifônico, a coisa complica. Digamos que temos 8 osciladores independentes.


Pressionamos uma nota isolada. Para onde ela vai?


Ela vai para o primeiro oscilador que não estiver ocupado.
Temos dois vetores que guardam, respectivamente, se o oscilador está ocupado e qual nota está sendo usada:



byte OSCILLATOR[8] = {0,0,0,0,0,0,0,0};


onde:

  • 0 significa oscilador livre
  • MIDI_NOTE guarda as últimas 8 notas válidas (entre 0 e 127)


O vetor OSCILLATOR vai ser consultado o tempo todo, para distribuir a nota para o oscilador livre. O OSCILLATOR[0] é o primeiro oscilador.  Uma vez que ele está livre, pode ser utilizado.
Ele estará ocupado até que receba um NOTE_OFF.



Como é feita a distribuição?


As notas, na ordem de chegada, vão pegando para si os osciladores livres. No momento que os NOTE_OFF vão chegando, vão liberando esses osciladores para uma nova nota. 



A prioridade é por preenchimento de valores mais baixos. Se os osciladores 1, 3, 5, 7 e 8 estiverem ocupados, o próximo a ser ocupado será o 2, depois o 4... e vai ocupando e conforme os osciladores. 


Uma distribuição aleatória (pegue qualquer um livre) também poderia funcionar. Não tenho argumentos para dizer se um tem vantagem sobre outro. Mas de qualquer forma a primeira implementação foi direta, preenchendo os slots com preferência  para os de menor número (osc 1, osc 2..).







RESUMO DO FUNCIONAMENTO:

Um Arduino Mestre controla vários Arduinos Escravos via protocolo serial.




Software:
No Arduino Mestre:  Biblioteca arduino_midi_library



Hardware:
No(s) Arduino(s) Escravo(s): Biblioteca mozzi e Biblioteca arduino_midi_library





Mozzi


audio synthesis library for Arduino







A biblioteca do Mozzi manipula o som e o MIDI (serial).
Temos o Mestre, distribuindo as notas entre os osciladores, desta maneira:
  1. O  RX de hardware do Arduino Mestre recebe MIDI de algum lugar (teclado, sequenciador etc);
  1. Os TX de hardware do Arduino Mestre manda informação para o(s) Arduino(s) escravo(s).




http://sensorium.github.io/Mozzi/examples/
O Mozzi usa a saída PWM do Arduino dessa forma, para gerar o áudio:




Saída padrão do Arduino/Mozzi





Existem muitas alternativas para melhorara a saída do sistema. Exemplos:


https://www.edn.com/design/analog/4460665/Fast-PWM-DAC-has-no-ripple


https://www.edn.com/design/analog/4459116/Cancel-PWM-DAC-ripple-with-analog-subtraction




Atualizando o código, esse abaixo não é diretamente relacionado com o projeto.

Aguarde uns dias para atualização (hoje 12/09/2018)


//==============================================================================
// Project: Mini_Mozzi_LAB
// Module: Arduino UNO
// V3.00
// 12/09/2018
//
// MIDI Library:
// https://github.com/FortySevenEffects/arduino_midi_library
//
// https://www.sound-machines.it/forums/topic/polyphonic-nanosynth-v1-0/
//
// Hardware
// 9 - Audio Out
// A0, A1, A2, A3 = Pots
// 2 = button
// 
// 
// 
//==============================================================================  


#pragma GCC optimize("-Ofast")
#pragma G++ optimize("-Ofast")


#include <MIDI.h>
#include <MozziGuts.h>
#include <Oscil.h> // oscillator template
#include <Line.h> // for envelope
#include <mozzi_midi.h>
#include <ADSR.h>
#include <mozzi_fixmath.h>

#include <tables/FENTUINO_1_4096_int8.h> //  table for oscillator
#include <tables/FENTUINO_2_4096_int8.h> //  table for oscillator
#include <tables/FENTUINO_3_4096_int8.h> //  table for oscillator
#include <tables/FENTUINO_4_4096_int8.h> //  table for oscillator


//==============================================================================  
// CONSTANTS
//==============================================================================  

// use #define for CONTROL_RATE, not a constant
#define CONTROL_RATE 128 // low to save processor

const char POT_1_PIN = 0; // set the input for the knob to analog pin 0
const char POT_2_PIN = 1; // set the input for the knob to analog pin 1
const char POT_3_PIN = 2; // set the input for the knob to analog pin 2
const char POT_4_PIN = 3; // set the input for the knob to analog pin 3

const int buttonPin = 2;     // the number of the pushbutton pin

#define ATTACK_LEVEL 255
#define DECAY_LEVEL 255

// use: Oscil <table_size, update_rate> oscilName (wavetable), look in .h file of table #included above

Oscil <FENTUINO_1_4096_NUM_CELLS, AUDIO_RATE> OSC1(FENTUINO_1_4096_DATA);
Oscil <FENTUINO_2_4096_NUM_CELLS, AUDIO_RATE> OSC2(FENTUINO_2_4096_DATA);
Oscil <FENTUINO_3_4096_NUM_CELLS, AUDIO_RATE> OSC3(FENTUINO_3_4096_DATA);
Oscil <FENTUINO_4_4096_NUM_CELLS, AUDIO_RATE> OSC4(FENTUINO_4_4096_DATA);


// envelope generators
// requires latest Mozzi (April 2014), enables envelope.next() at control rate, using latest version of Mozzi
// use: ADSR <unsigned int CONTROL_UPDATE_RATE, unsigned int LERP_RATE> envName;

ADSR <CONTROL_RATE, CONTROL_RATE> envelope;

int gain;

int buttonState = 0;         // variable for reading the pushbutton status

int POT_1_VALUE = 0;
int POT_2_VALUE = 0;
int POT_3_VALUE = 0;
int POT_4_VALUE = 0;

unsigned int POT_1 = 0;
unsigned int POT_2 = 0;
unsigned int POT_3 = 0;
unsigned int POT_4 = 0;

unsigned int ATTACK = 500; // long enough for control rate to catch it
unsigned int DECAY = 50;
unsigned int SUSTAIN = 30000; // Sustain 60 seconds unless a noteOff comes.
unsigned int RELEASE = 500;







void setup(){
  startMozzi(CONTROL_RATE); // set a control rate of 64 (powers of 2 please)
  
  pinMode(buttonPin, INPUT_PULLUP);

  envelope.setADLevels(ATTACK_LEVEL,DECAY_LEVEL);
  envelope.setTimes(ATTACK,DECAY,SUSTAIN,RELEASE); 
}




void updateControl(){
  
  POT_1_VALUE = mozziAnalogRead(POT_1_PIN); // value is 0-1023
  OSC1.setFreq(POT_1_VALUE + 100);
  OSC2.setFreq(POT_1_VALUE + 100);
  OSC3.setFreq(POT_1_VALUE + 100);
  OSC4.setFreq(POT_1_VALUE + 100);

  POT_2_VALUE = mozziAnalogRead(POT_2_PIN); // value is 0-3
  POT_2_VALUE = POT_2_VALUE >>8;

  POT_3_VALUE = mozziAnalogRead(POT_3_PIN); // value is 0-1023
  ATTACK = POT_3_VALUE << 2;
  
  POT_4_VALUE = mozziAnalogRead(POT_4_PIN); // value is 0-1023
  RELEASE = POT_4_VALUE << 2;
  DECAY = RELEASE;
  
  envelope.setTimes(ATTACK,DECAY,SUSTAIN,RELEASE); 

  envelope.update();

  gain = envelope.next() ;



buttonState = digitalRead(buttonPin);
if (buttonState == 0) envelope.noteOn();
if (buttonState == 1) envelope.noteOff();

  

}



int updateAudio(){

if (POT_2_VALUE == 0) return ((OSC1.next() * gain)>>8) ;
if (POT_2_VALUE == 1) return ((OSC2.next() * gain)>>8) ;
if (POT_2_VALUE == 2) return ((OSC3.next() * gain)>>8) ;
if (POT_2_VALUE == 3) return ((OSC4.next() * gain)>>8) ;



}


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












==================================================================

Arduino Nano na base
Suporte para as bases
Suporte para as bases montado



Base montada e futura caixa
Teste do Arduino Master (Uno) com MIDI Shield
Primeira montagem com 8 osciladores