/*****************************************************************************
** MEGATRON 0-4-2 **
*****************************************************************************
** THIS AN ALPHA VERSION USE AT YOUR OWN RISK !! **
** **
** Le projet megatron s'inspire d'autres projets sur le net, merci à eux : **
*****************************************************************************
** La gestion de l'EEPROM est basée sur cet excellent article : **
** http://tronixstuff.wordpress.com/2010/10/29/tutorial-arduino-and-the-i2c-bus-part-two/
*****************************************************************************
** La gestion du monotron via le DAC MCP4822 est basée sur ce projet : **
** http://code.google.com/p/denkitribe/source/browse/trunk/Arduino/MonotronCVGateTest/MonotronCVGateTest.pde
** http://picasaweb.google.com/lh/photo/n7IeK9KFVEkoU1dxL1zYLQ?feat=twitter**
** http://www.youtube.com/watch?v=n1UpleRN2gc **
*****************************************************************************
** **
** Merci à tous ceux qui publient leur code sur internet ! **
** **
*****************************************************************************
***** By 23-7 Lab ***** http://23-7.net/ ***** http://lelabo.xooit.com/ *****
** **
** An arduino sequencer for modded monotron **
** **
** Aout 2011 **
*****************************************************************************
** MEGATRON is free software: you can redistribute it and/or modify **
** it under the terms of the GNU General Public License as published by **
** the Free Software Foundation, either version 3 of the License, or **
** (at your option) any later version. **
** **
** MEGATRON is distributed in the hope that it will be useful, **
** but WITHOUT ANY WARRANTY; without even the implied warranty of **
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the **
** GNU General Public License for more details. **
** **
** along with MEGATRON. If not, see . **
*****************************************************************************/
// On charge les librairies LiquidCrystal pour l'écran LCD (16x2) SPI pour
// le DAC (MP4822),MSTimer2 pour le tempo et Wire pour la mémoire externe
// EEPROM (24LC256)
#include
#include
#include
#include
// L'adresse sur le bus I2C de l'EEPROM de mémoire externe
// 24LC256 relié sur les Pins Analog 4 et 5 (I2C) toutes les pins
// d'adresses de l'EEPROM reliées à la masse
#define memoire01 0x50
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// !!! REGLEZ ICI LA VITESSE EN FONCTION DE LA CONNECTION UTILISEE POUR LA !!!
// !!! SYNCHRO : !!!
// !!! [x] via midi : 31250 !!!
// !!! [ ] via usb / processing : 38400 !!!
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
#define baud 31250
// On définit les constante pour les connections
// Pour plus de détail sur les entrées gate et cv
// du monotron rendez vous ici :
// http://www.dinsync.info/2010/06/how-to-modify-korg-monotron.html
// Gate Pin
const byte kGatePin = 2;
// SS Pin for MP4822 Dac (SPI)
const byte kSSPin = 10;
// Les touches du claviers sont relié via un pont diviseur de tension sur une entrée analogique
const byte analogmenu = 0;
// Le potentiomètre d'entrée de valeur est relié sur une entrée analogique
const byte valpin = 2;
//La position actuelle dans la séquence
byte POS=0;
//Tableau permettant de stocker les différents "pattern"
int seq[16][16][2];
int tempo=120;
byte ppq=0;
// Nombre de "pas" à lire de la sequence
// cette variable n'est pas lié au tableau de sequence
// ainsi en changeant de pattern on boucle toujours sur
// le meme nombre de pas
byte fin=15;
// Le pas en cours d'édition
byte editpos=0;
// Temps de maintien de la note en pourcentage d'un pas
byte gate=80;
// Le pattern en cours
byte pattern=0;
int tempoMS;
int value=0;
int pitchvalue=0;
int start,menud,menud1;
byte temps=0;
boolean sens=0;
boolean st=1;
boolean f=0;
/*********************************************************************
** Les différents modes du megatron **
** 0 : Edition du tempo - START/STOP **
** 1 : Choix / Sauvegarde du pattern **
** 2 : Valeur de la note **
** 3 : Durée (en pas) de la note **
** 4 : Nombre de pas de la boucle **
** 5 : Temps de maintien de la note (en pourcentage d'un pas) **
** 6 : sous menu : Fonctions : **
** ------ 1 : RANDOM **
** ------ 2 : SHIFT **
** ------ 3 : ERASE **
** ------ 4 : SENS (de lecture) **
** ------ 5 : EXIT **
**********************************************************************/
int mode=0;
// Charactères personnalisés pour l'affichage de la séquence
byte seqchar[8] = { B00000, B10001, B00000, B00000, B10001, B01110, B00000,};
byte seqchari[8] = { B11111, B01110, B11111, B11111, B01110, B10001, B11111, };
// On initialise le LCD
// il connecté ainsi :
// RS : 3
// E : 4
// DB4-7 : 5-8
LiquidCrystal lcd(3, 4, 5, 6, 7,8);
/**************************************
* Cette fonction permet d'écrire des *
* données data à l'adresse adress de *
* l'EEPROM device *
**************************************/
void writeData(int device, unsigned int adress, byte data) {
Wire.beginTransmission(device);
// L'adresse sur 16 bit est transmise en deux byte (8 bits)
Wire.send((int)(adress >> 8)); // MSB
Wire.send((int)(adress & 0xFF)); // LSB
Wire.send(data);
Wire.endTransmission();
delay(10);
}
/****************************************
* Cette fonction renvoie un byte *
* contenant les données situés à *
* l'adresse adress sur l'EEPROM device *
****************************************/
byte readData(int device, unsigned int adress) {
byte result; // Les données renvoyés (8 bits)
Wire.beginTransmission(device);
// L'adresse sur 16 bits est transmise en deux bytes (8bits)
Wire.send((int)(adress >> 8)); // MSB
Wire.send((int)(adress & 0xFF)); // LSB
Wire.endTransmission();
Wire.requestFrom(device,1); // On réclame les données
result = Wire.receive();
return result; // on retourne les données
}
/************************************************
* Cette fonction sauvegarde le pattern actuel *
* sur l'EEPROM *
************************************************/
void savepattern() {
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Save P ");
lcd.print(pattern);
for (int i=0; i<16;i++) {
// Sauvegarde des valeurs de notes (on divise par 4 afin de rester sur 8bits
writeData(memoire01,(pattern*16)+i,(seq[pattern][i][0])/4);
delay(10);
// Sauvegarde de la durée des pas
writeData(memoire01,272+((pattern*16)+i),seq[pattern][i][1] % 16);
lcd.setCursor(i,1);
lcd.write(1);
delay(10);
}
lcd.clear();
}
/*****************************************
* Cette fonction gére l'affichage des *
* menus sur le LCD 16x2 *
*****************************************/
void screen() {
pitchvalue = analogRead(valpin) ;
// F==0 -> Main Menu
// F==1 -> Functions SubMenus
if (f==0) {
switch(mode) {
case 0:
lcd.setCursor(0,1);
lcd.print("T: ");
lcd.print(tempo);
lcd.print(" Bpm ");
// Pour le debuggage on affiche la valeur du pattern / la valeur du bouton
lcd.print(pitchvalue/4);
lcd.print(" ");
break;
case 1:
lcd.setCursor(0,1);
lcd.print("Pattern: ");
lcd.print((int)pattern);
lcd.print("/ 16 ");
break;
case 2:
lcd.setCursor(0,1);
lcd.print("P: ");
lcd.print(seq[pattern][editpos][0]);
lcd.print("/");
lcd.print(pitchvalue);
lcd.print(" ");
break;
case 3:
lcd.setCursor(0,1);
lcd.print("D :");
lcd.print(seq[pattern][editpos][1]);
lcd.print(" / ");
lcd.print(pitchvalue/64);
lcd.print(" ");
break;
case 4:
lcd.setCursor(0,1);
lcd.print("End : ");
lcd.print((int)fin);
lcd.print(" - ");
lcd.print(pitchvalue/64);
lcd.print(" ");
break;
case 5:
lcd.setCursor(0,1);
lcd.print("G: ");
lcd.print((int)gate);
lcd.print("/");
lcd.print(pitchvalue/10);
lcd.print(" ");
break;
case 6:
lcd.setCursor(0,1);
lcd.print("-FUNCTIONS");
break;
}
} else {
lcd.setCursor(0,0);
switch(mode) {
case 0:
lcd.print("-Random");
lcd.setCursor(3,1);
lcd.print("Cur");
lcd.setCursor(9,1);
lcd.print("All");
break;
case 1:
lcd.print("-Shift");
lcd.setCursor(3,1);
lcd.print("<<<");
lcd.setCursor(9,1);
lcd.print(">>>");
break;
case 2:
lcd.print("-Erase");
lcd.setCursor(3,1);
lcd.print("Cur");
lcd.setCursor(9,1);
lcd.print("All");
break;
case 3:
lcd.print("-SENS");
lcd.setCursor(3,1);
lcd.print(">>");
lcd.setCursor(9,1);
lcd.print("<<");
break;
case 4:
lcd.setCursor(4,1);
lcd.print("-EXIT");
break;
}
}
}
/***********************************************
* cette fonction gère les modes et le clavier *
***********************************************/
void AnalogMenu() {
// Afin d'éviter les doubles par erreur on n'accepte une commade toutes les 70ms
// valeur à adapter à vos gros doigts :p
menud1=millis();
if (menud1-menud > 200) {
value = analogRead(analogmenu);
if (f==0) {
if (value > 180) {
if (value < 250) {
// Bouton 1 Mode-
if (mode>0) {
mode--;
} else {
mode = 6;
}
//On change de mode donc on renouvelle l'affichage
screen();
}
if ((value < 320) && (value > 250)) {
//bouton 2 (la fonction varie selon le mode)
switch(mode) {
case 0:
// Le bouton est un start / stop
if (st == 0) {
st = 1;
Serial.write(0xFA);
sendNoteOff(10);
} else {
st = 0;
Serial.write(0xFC);
delay(10);
Serial.write(0xFC);
}
POS=0;
break;
case 1:
// En mode choix / sauvegarde de pattern le bouton sert à
// à choisir le pattern
pattern++;
pattern= pattern%16;
break;
case 2:
// En mode d'édition de valeur de note le bouton sert à
// choisir le pas édité
editpos++;
editpos= editpos % (fin+1);
// On change de pas edité on renouvelle donc l'affichage de
// la première ligne
lcd.setCursor(editpos-1,0);
lcd.print(" ");
break;
case 3:
// En mode d'édition de la durée d'un pas le bouton sert
// à choisir le pas édité
editpos++;
editpos= editpos % (fin+1);
// On change le pas édité on renouvelle donc la première
// ligne de l'affichage
lcd.setCursor(editpos-1,0);
lcd.print(" ");
break;
case 4:
// Dans ce mode le bouton ser à modifier le pas de bouclage
// par pas de 1
fin++;
fin=fin%16;
break;
case 5:
// Dans ce mode le bouton sert à modifier la variable par pas de 1
gate++;
gate=gate%99;
break;
}
}
if (value > 300) {
if (value < 400) {
// Le 3 eme bouton est lié à la valeur du potentiomètre de valeur
pitchvalue = analogRead(valpin) ;
switch(mode) {
case 0:
// En mode édition de tempo l'appuie sur le bouton fait passer la
// valeur du potentiomètre / 4 ( 0 - 256) dans la variable tempo
tempo=pitchvalue/4;
tempoMS = (60000L / (tempo))/24;
MsTimer2::stop();
MsTimer2::set(tempoMS, sync);
MsTimer2::start();
break;
case 1:
// En mode choix / sauvegarde le bouton sert à appeller la
// sauvegarde du pattern courant
savepattern();
break;
case 2:
// Le bouton fait passer la valeur du potentiomètre dans la
// valeur de note du pas actuellement edité
seq[pattern][editpos][0]=pitchvalue;
break;
case 3:
// Le bouton fait passer la valeur du potentiomètre / 64 (0-16) dans la
// varialbe de durée de pas du pas actuellement édité
seq[pattern][editpos][1]=pitchvalue/64;
break;
case 4:
// Le bouton fait passer la valeur du potentiomètre / 64
// dans la variable de pas de bouclage
fin = pitchvalue / 64 ;
break;
case 5:
// Le bouton fait passer la valeur du potentiomètre / 10 % 100 (0/100)
// dans la variable de temps de maintien de la note
gate =(pitchvalue / 10)%100 ;
break;
case 6:
lcd.clear();
f=1;
mode=0;
screen();
break;
}
// On renouvelle l'affichage
screen();
} else {
// Bouton 4 mode +
if (mode<6) {
mode++;
} else {
mode = 0;
}
// On change de mode on renouvelle donc l'affichage
screen();
}
}
}
} else {
if ((value > 180) && (value < 250)) {
lcd.clear();
if (mode==0) {
mode = 4;
} else {
mode --;
}
}
if ((value > 250 ) && (value < 320 )) {
switch(mode) {
case 0:
RndCur();
break;
case 1:
ShiftL();
break;
case 2:
ErCur();
break;
case 3:
sens=0;
break;
case 4:
lcd.clear();
mode=0;
screen();
f=0;
break;
}
}
if ((value > 320 ) && (value < 400)) {
switch(mode) {
case 0:
RndAll();
break;
case 1:
ShiftR();
break;
case 2:
ErAll();
break;
case 3:
sens=1;
break;
case 4:
mode=0;
lcd.clear();
screen();
f=0;
break;
}
}
if (value > 400) {
lcd.clear();
if (mode==4) {
mode = 0;
} else {
mode ++;
}
}
}
menud=millis();
}
}
/**************************************
* Cette fonction place des valeurs *
* aléatoire dans le pattern courant *
**************************************/
void RndCur() {
lcd.clear();
lcd.setCursor(0,0);
lcd.print("rnd current");
for(int i=0;i<16;i++) {
seq[pattern][i][0]=random(1023);
seq[pattern][i][1]=random(4);
lcd.setCursor(i,1);
lcd.print("#");
}
delay(500); // un peu de délai pour éviter de lancer la fonction plusieurs fois d'affilés
}
/***********************************************
* Cette fonction place des valeurs aléatoires *
* dans tous les patterns de la mémoires *
***********************************************/
void RndAll() {
lcd.clear();
for(int i=0;i<16;i++) {
for(int j=1;j<16;j++) {
seq[i][j][0]=random(1023);
seq[i][j][1]=random(4);
lcd.setCursor(j,0);
lcd.print("#");
delay(10);
}
lcd.setCursor(i,1);
lcd.print("#");
for(int k=0;k<16;k++) {
lcd.setCursor(k,0);
lcd.print(" ");
}
delay(10);
}
delay(500);
}
/************************************************
* cette fonction décalle toutes les notes et *
* durées d'un pas vers la gauche *
************************************************/
void ShiftL() {
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Shifting ...");
int buf1=seq[pattern][0][0];
int buf2=seq[pattern][0][1];
for (int i=14;i==0;i--) {
lcd.setCursor(i+1,1);
lcd.print("<");
seq[pattern][i+1][0]=seq[pattern][i][0];
seq[pattern][i+1][1]=seq[pattern][i][1];
delay(20);
}
seq[pattern][15][0]=buf1;
seq[pattern][15][1]=buf2;
lcd.setCursor(9,0);
lcd.print(" Done"),
delay(500);
}
/************************************************
* cette fonction décalle toutes les notes et *
* durées d'un pas vers la gauche *
************************************************/
void ShiftR() {
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Shifting ...");
int buf1=seq[pattern][15][0];
int buf2=seq[pattern][15][1];
for (int i=1;i<16;i++) {
lcd.setCursor(i-1,1);
lcd.print(">");
seq[pattern][i-1][0]=seq[pattern][i][0];
seq[pattern][i-1][1]=seq[pattern][i][1];
delay(20);
}
seq[pattern][0][0]=buf1;
seq[pattern][0][1]=buf2;
lcd.setCursor(9,0);
lcd.print(" Done"),
delay(500);
}
/***********************************
* Cette fonction met les valeurs *
* de pitch et de durée à 0 dans *
* le pattern courant *
***********************************/
void ErCur() {
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Erase ");
lcd.print(pattern);
for (int i=0; i<16;i++) {
lcd.setCursor(i,1);
lcd.print("1");
}
for (int i=0;i<16;i++) {
lcd.setCursor(i,1);
lcd.print("0");
seq[pattern][i][0]=0;
seq[pattern][i][1]=0;
delay(20);
}
lcd.setCursor(10,0);
lcd.print("Done");
delay(500);
}
/***********************************
* Cette fonction met les valeurs *
* de pitch et de durée à 0 dans *
* le pattern courant *
***********************************/
void ErAll() {
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Erase all");
for (int i=0; i<16;i++) {
lcd.setCursor(i,1);
lcd.print("1");
}
for (int i=0; i<16;i++) {
for(int j=0; j<16;j++) {
seq[i][j][0]=0;
seq[i][j][1]=0;
lcd.setCursor(j,1);
lcd.print("0");
delay(10);
}
lcd.setCursor(10,0);
lcd.print(" ");
lcd.print(i);
lcd.print(" ");
delay(100);
for (int k=0; k<16;k++) {
lcd.setCursor(k,1);
lcd.print("1");
}
delay(10);
}
delay(500);
}
/***************************************************
* cette fonction envoie une tension de controle *
* via le DAC au monotron *
***************************************************/
void sendNoteOn(int key) {
// La valeur transmise est comprise entre 0 et 1023 le DAC attends une valeur entre 0 et 4095 (12 bits
key=key*4;
// On n'envoie une note que si le pitch est supérieur à 0
// un pitch de 0 est équivalent à un silence
if (key>0) {
digitalWrite(kGatePin, HIGH); // On place le GATE à haut
digitalWrite(kSSPin, LOW); // On prépare le DAC à recevoir des données
// On transfert la valeur et l'adresse sur un meme int(16 bit)
// mais la transmition série se fait par byte (8 bit)
// on sépare donc l'int en 2 part
SPI.transfer(0x10 + (key >> 8)); // MSB
SPI.transfer(key & 0xff); // LSB
digitalWrite(kSSPin, HIGH); // On signale au dac que le transfert de données est terminé
}
}
/**********************************************
* Cette fonction envoie un signal "note off" *
* en baissant la tension sur la sortie gate *
* après avoir attendu un temps de maintien *
* decay *
**********************************************/
void sendNoteOff(int decay) {
delay(decay);
digitalWrite(kGatePin, LOW);
}
/**********************************************
* Cette fonction envoie la synchro midi et *
* appelle les envois de note quand il y a *
* Besoin. *
**********************************************/
void sync() {
// Cette fonction est appellée par l'interuption du timer
// le code doit rester le plus concis possible.
if (st ==1) {
// On envoie 24 tick de clock par 1/4 de noire
// 1 Pas = 1/16 de note
if (ppq<6) {
// 0xF8 -> Midi Clock Status byte
Serial.write(0xF8);
ppq++;
} else {
Serial.write(0xF8);
ppq=0;
sendNoteOn(seq[pattern][POS][0]);
if (temps < seq[pattern][POS][1]+1) {
temps++;
} else {
temps=0;
// decay : gate% d'un pas
sendNoteOff(tempoMS-((tempoMS*(100-gate))/100));
POS++;
}
if(POS>fin) {
POS=0;
}
}
}
}
void setup() {
Serial.begin(baud);
/* On initialise les variables */
POS=0;
int enter=0;
/* On configure les pins digitales de sorties */
pinMode (kSSPin, OUTPUT);
pinMode(kGatePin, OUTPUT);
for(int i=3;i<10;i++) {
pinMode(i,OUTPUT);
}
// On initialise les librairies SPI pour le DAC et Wire pour l'EEPROM
SPI.begin();
Wire.begin();
/* On initialise le LCD 16x2 charactères, on crée les charactère
* personnalisés et on efface l'écran */
lcd.begin(16, 2);
lcd.createChar(0, seqchar);
lcd.createChar(1, seqchari);
lcd.clear();
/* On remplit le tableau de pattern grace aux données de l'EEPROM */
for (int i=0; i<16;i ++) {
lcd.setCursor(0,1);
lcd.print("Load ");
lcd.print(i);
lcd.print("/16");
for (int j=0; j<16;j++) {
// on traduit l'adresse à 2 coordonnées i,j du tableau en adresse
// linéaire pour l'eeprom i * 16 + j (16 étant la taille maximale
// de la première coordonée
seq[i][j][0] = readData(memoire01,((i*16)+j))*4;
seq[i][j][1] = readData(memoire01, 272 + ((i*16)+j)) % 16;
// Cette ligne envoie une note, cette fioriture est complètement inutile :p
sendNoteOn((i*16+j)*3);
lcd.setCursor(11,1);
lcd.print(" ");
lcd.print(seq[i][j][1]);
lcd.print(" ");
lcd.setCursor(j,0);
lcd.write(1);
delay(10);
}
lcd.clear();
delay(10);
}
// Fin de la fioriture inutile
sendNoteOff(50);
lcd.clear();
/* On attend que l'utilisateur presse le bouton 3
On mémorise à quel moment cela se produit */
int flash=0;
while(enter == 0) {
/* Petit écran d'acceuil */
if (flash < 50) {
flash++;
lcd.setCursor(0,0);
lcd.print("hello my name is");
lcd.setCursor(3,1);
lcd.print("MEGATRON 0.42");
} else {
flash = 0;
lcd.clear();
lcd.setCursor(0,0);
lcd.print("hello my name is");
lcd.setCursor(3,1);
lcd.print("PRESS BTN 3");
}
value = analogRead(analogmenu);
if ((value >300) && (value <400)) {
enter = 127;
break;
}
}
// On attache la fonction sync à l'interruption du timer
// Grace a la librairie MSTimer2
MsTimer2::set((60000L / (tempo))/24, sync);
MsTimer2::start();
// ce delai sert à éviter que l'appuie sur la touche 3 ne soit
// pris en compte par la gestion de menu
// Adapter le temps à vos gros doigts ;)
delay(500);
lcd.clear();
}
void loop() {
if (f==0) {
// Si on est dans le menu principale on affiche
// le curseur de position joué et édité
if (POS>0) {
lcd.setCursor(POS-1,0);
lcd.print(" ");
lcd.write(1);
lcd.setCursor(editpos,0);
lcd.write(0);
} else {
lcd.setCursor(fin,0);
lcd.print(" ");
lcd.setCursor(0,0);
lcd.write(1);
}
if (fin<15) {
lcd.setCursor(fin+1,0);
lcd.print("<");
}
}
screen(); // Gestion de l'affichage
AnalogMenu(); // Gestion du clavier
}