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:
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.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.
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.
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.
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:
- Evita problemas de aterramento
- Evita acidentes elétricos
- Evita ruídos indesejados
1) Cada um monta o seu:
Esquema da entrada MIDI para o arduino |
MIDI Shield típico. 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:
- O RX de hardware do Arduino Mestre recebe MIDI de algum lugar (teclado, sequenciador etc);
- 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 |