/***************************************************************************** ** 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 }