Música amb Arduino

De Wikijoan
Dreceres ràpides: navegació, cerca

Contingut

hello world audio (fer sonar una nota), amb altaveu

el més senzill que puc fer:

//Arduino Sound Hello World
//Created by David Fowler of uCHobby.com
//Define the I/O pin we will use for our sound output
#define SOUNDOUT_PIN 11

void setup(void){
//Set the sound out pin to output mode
pinMode(SOUNDOUT_PIN,OUTPUT);
}

void loop(void){
//Generate sound by toggling the I/O pin High and Low
//Generate a 1KHz tone. set the pin high for 500uS then
//low for 500uS to make the period 1ms or 1KHz.

//Set the pin high and delay for 1/2 a cycle of 1KHz, 500uS.
digitalWrite(SOUNDOUT_PIN,HIGH);
delayMicroseconds(100);

//Set the pin low and delay for 1/2 a cycle of 1KHz, 500uS.
digitalWrite(SOUNDOUT_PIN,LOW);
delayMicroseconds(100);
}

Tant funciona amb el pin 11 PWM com amb el pin digital 53. En el pin de sortida col.loco una resistència de 1K, i l'altaveu. El terra de l'altaveu va el terra de la sortida. Se sent mooolt fluixet. Faig proves amb 500 us, 300us i 100us. Sembla ser que el speaker hauria de ser d'uns 8ohms, i crec que el speaker de què disposo és de molt més. Provar amb uns auriculars dels petits.

Finalment ja tinc un speaker amb impedància de 8 ohms (que ve de l'ordinador antic), i funciona a la perfecció. Ficant la resistència de 1K sona una mica més fluix.

Arduino: Tone library

per produir ones quadrades i poder tocar notes.

Fixem-nos que amb el ATmega1280 disposo de 6 timers, i això vol dir que puc produir 6 notes simultànies. En el Arduino Mega que faig servir també hi ha una sortida 13 que és PWM, i per tant el codi no el canvio. Aquí és on ficaré el speaker, amb una resistència de 1K (es pot veure un dibuix a http://itp.nyu.edu/physcomp/Labs/ToneOutput)

Per fer l'exemple més simple de la llibreria Tone.h segueixo http://code.google.com/p/arduino-tone/

Em descarrego la llibreria i la instal.lo.

L'exemple més senzill que podem fer:

#include <Tone.h>

Tone tone1;

void setup()
{
  tone1.begin(13);
  tone1.play(NOTE_A4);
}

void loop()
{
}

i un exemple més currat que utilitza dos speakers. Per fer sonar aquest projecte hem d'enviar les notes a través de la consola sèrie. Podem utilitzar tant la consola sèrie que ve amb el SDK de l'arduino, o millor, utilitzar el minicom. En el cas del minicom, senzillament és escriure la nota que vols tocar, i Enter.

Hi ha dos altaveus, i d'aquesta manera tenim bi-polifonia. Els caràcters a-g van pel primer altaveu, i les notes A-G van pel segon altaveu. Per fer parar una nota, s'utilitza s o S. Aquest codi de fet és molt fàcil de modificar per ampliar les polifonies o augmentar el rang de notes.

Allò recomanable és posar una resistència de 1K a cada sortida (11 i 12 en el nostre cas), i connectar les resistències a l'altaveu (amb un sol altaveu aconseguim la polifonia). D'aquesta manera aïllem les diferents entrades, tot i que el volum queda molt reduït.

// Duelling Tones - Simultaneous tone generation.
// To mix the output of the signals to output to a small speaker (i.e. 8 Ohms or higher),
// simply use 1K Ohm resistors from each output pin and tie them together at the speaker.
// Don't forget to connect the other side of the speaker to ground!

// This example plays notes 'a' through 'g' sent over the Serial Monitor.
// 's' stops the current playing tone.  Use uppercase letters for the second.

#include <Tone.h>

int notes[] = { NOTE_A3,
                NOTE_B3,
                NOTE_C4,
                NOTE_D4,
                NOTE_E4,
                NOTE_F4,
                NOTE_G4 };

// You can declare the tones as an array
Tone notePlayer[2];

void setup(void)
{
  Serial.begin(9600);
  notePlayer[0].begin(11);
  notePlayer[1].begin(12);
}

void loop(void)
{
  char c;

  if(Serial.available())
  {
    c = Serial.read();
    
    switch(c)
    {
      case 'a':
      case 'b':
      case 'c':
      case 'd':
      case 'e':
      case 'f':
      case 'g':
        notePlayer[0].play(notes[c - 'a']);
        Serial.println(notes[c - 'a']);
        break;
      case 's':
        notePlayer[0].stop();
        break;

      case 'A':
      case 'B':
      case 'C':
      case 'D':
      case 'E':
      case 'F':
      case 'G':
        notePlayer[1].play(notes[c - 'A']);
        Serial.println(notes[c - 'A']);
        break;
      case 'S':
        notePlayer[1].stop();
        break;

      default:
        notePlayer[1].stop();
        notePlayer[0].play(NOTE_B2);
        delay(300);
        notePlayer[0].stop();
        delay(100);
        notePlayer[1].play(NOTE_B2);
        delay(300);
        notePlayer[1].stop();
        break;
    }
  }
}

hello world amb bronzidor (buzzer)

un bronzidor-zumbador-buzzer no és el mateix que un altaveu. És de fet un sensor piezoelectric, que suposo que té una carcassa de plàstic amb la finalitat de què ressoni el bronzit-soroll. Els piezos normalment els he fet servir per detectar cops o pressió: una pressió genera una diferència de potencial. També es pot fer servir al revés: una diferència de potencial dóna lloc a un moviment. Per produir un so a una freqüència determinada, he d'aplicar una diferència de potencial alterna, i això s'aconsegueix amb un timer tal com es veu en el codi que ve.

A la botiga hi ha molts bronzidors-piezos a escollir, i el que tinc, que va bé, és un 12V DC (també n'hi ha d'alterns), que de fet treballa entre 1.5V i 15V. En l'arduino es treballa a 5V, i el soroll que produeix el piezo és força potent.

El codi no necessita cap llibreria, i és tant simple com connectar el piezo al pin 9 (cable vermell, positiu, doncs els piezos tenen polaritat), i el negre al GND. És el pin 9 de la sortida digital (PWM). En aquest exemple no fem servir el fet de que la sortida sigui PWM, però en el exemple següent utilitzarem aquesta propietat per poder controlar el volum.

int speakerPin = 9;

int length = 15; // the number of notes
char notes[] = "ccggaagffeeddc "; // a space represents a rest
int beats[] = { 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 4 };
int tempo = 300;

void playTone(int tone, int duration) {
  for (long i = 0; i < duration * 1000L; i += tone * 2) {
    digitalWrite(speakerPin, HIGH);
    delayMicroseconds(tone);
    digitalWrite(speakerPin, LOW);
    delayMicroseconds(tone);
  }
}

void playNote(char note, int duration) {
  char names[] = { 'c', 'd', 'e', 'f', 'g', 'a', 'b', 'C' };
  int tones[] = { 1915, 1700, 1519, 1432, 1275, 1136, 1014, 956 };

  // play the tone corresponding to the note name
  for (int i = 0; i < 8; i++) {
    if (names[i] == note) {http://www.arduino.cc/en/Tutorial/Melody
      playTone(tones[i], duration);
    }
  }
}

void setup() {
  pinMode(speakerPin, OUTPUT);
}

void loop() {
  for (int i = 0; i < length; i++) {
    if (notes[i] == ' ') {
      delay(beats[i] * tempo); // rest
    } else {
      playNote(notes[i], beats[i] * tempo);
    }

    // pause between notes
    delay(tempo / 2); 
  }
}

En la pàgina web de referència que he fet servir (http://www.arduino.cc/en/Tutorial/Melody) i d'on he tret el codi es veu la correspondència entre les notes, la freqüència de la nota, i la pulsació que s'ha d'aplicar al piezo:

This example uses a piezo speaker to play melodies. It sends a square wave of the appropriate frequency to the piezo, generating the corresponding tone.

The calculation of the tones is made following the mathematical operation:

      timeHigh = period / 2 = 1 / (2 * toneFrequency)

where the different tones are described as in the table:http://www.arduino.cc/en/Tutorial/Melody

note    frequency    period    timeHigh
c       261 Hz       3830       1915 	
d       294 Hz       3400       1700 	
e       329 Hz       3038       1519 	
f       349 Hz       2864       1432 	
g       392 Hz       2550       1275 	
a       440 Hz       2272       1136 	
b       493 Hz       2028       1014	
C       523 Hz       1912        956

una plata d'enciam

A partir d'aquí puc adaptar aquest codi per fer sonar la melodia Una Plata d'Enciam:

int melody[] = {  e,  f,  g,  g,  a,  a,  g,  a,  a,  g, f, f, f, f, f, g, e, e, f, g, g, a, a, g,  a,  a,  g, f, f, f, g, e };
int beats[]  = {  3,  3,  3,  3,  3,  3,  6,  2,  2,  2, 3, 3, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 6,  2,  2,  2, 3, 3, 3, 3, 6};
// la proporció entre corxees, corxees del treset i negres és 3:2:6, que és el que necessiot per a la cançó. 2 és el MCD de 2,3,6, doncs no puc fer servir decimals

És el fitxer buzzer_una_plata_denciam.pde

una altra possibilitat

Veure també el primer exemple de http://www.arduino.cc/en/Tutorial/PlayMelody:

/* Play Melody
 * -----------
 *
 * Program to play a simple melody
 *
 * Tones are created by quickly pulsing a speaker on and off 
 *   using PWM, to create signature frequencies.
 *
 * Each note has a frequency, created by varying the period of 
 *  vibration, measured in microseconds. We'll use pulse-width
 *  modulation (PWM) to create that vibration.

 * We calculate the pulse-width to be half the period; we pulse 
 *  the speaker HIGH for 'pulse-width' microseconds, then LOW 
 *  for 'pulse-width' microseconds.
 *  This pulsing creates a vibration of the desired frequency.
 *
 * (cleft) 2005 D. Cuartielles for K3
 * Refactoring and comments 2006 clay.shirky@nyu.edu
 * See NOTES in comments at end for possible improvements
 */

// TONES  ==========================================
// Start by defining the relationship between 
//       note, period, &  frequency. 
#define  c     3830    // 261 Hz 
#define  d     3400    // 294 Hz 
#define  e     3038    // 329 Hz 
#define  f     2864    // 349 Hz 
#define  g     2550    // 392 Hz 
#define  a     2272    // 440 Hz 
#define  b     2028    // 493 Hz 
#define  C     1912    // 523 Hz 
// Define a special note, 'R', to represent a rest
#define  R     0

// SETUP ============================================
// Set up speaker on a PWM pin (digital 9, 10 or 11)
int speakerOut = 9;
// Do we want debugging on serial out? 1 for yes, 0 for no
int DEBUG = 1;

void setup() { 
  pinMode(speakerOut, OUTPUT);
  if (DEBUG) { 
    Serial.begin(9600); // Set serial out if we want debugging
  } 
}

// MELODY and TIMING  =======================================
//  melody[] is an array of notes, accompanied by beats[], 
//  which sets each note's relative length (higher #, longer note) 
int melody[] = {  C,  b,  g,  C,  b,   e,  R,  C,  c,  g, a, C };
int beats[]  = { 16, 16, 16,  8,  8,  16, 32, 16, 16, 16, 8, 8 }; 
int MAX_COUNT = sizeof(melody) / 2; // Melody length, for looping.

// Set overall tempo
long tempo = 10000;
// Set length of pause between notes
int pause = 1000;
// Loop variable to increase Rest length
int rest_count = 100; //<-BLETCHEROUS HACK; See NOTES

// Initialize core variables
int tone = 0;
int beat = 0;
long duration  = 0;

// PLAY TONE  ==============================================
// Pulse the speaker to play a tone for a particular duration
void playTone() {
  long elapsed_time = 0;
  if (tone > 0) { // if this isn't a Rest beat, while the tone has 
    //  played less long than 'duration', pulse speaker HIGH and LOW
    while (elapsed_time < duration) {

      digitalWrite(speakerOut,HIGH);
      delayMicroseconds(tone / 2);

      // DOWN
      digitalWrite(speakerOut, LOW);
      delayMicroseconds(tone / 2);

      // Keep track of how long we pulsed
      elapsed_time += (tone);
    } 
  }
  else { // Rest beat; loop times delay
    for (int j = 0; j < rest_count; j++) { // See NOTE on rest_count
      delayMicroseconds(duration);  
    }                                
  }                                 
}

// LET THE WILD RUMPUS BEGIN =============================
void loop() {
  // Set up a counter to pull from melody[] and beats[]
  for (int i=0; i<MAX_COUNT; i++) {
    tone = melody[i];
    beat = beats[i];

    duration = beat * tempo; // Set up timing

    playTone(); 
    // A pause between notes...
    delayMicroseconds(pause);

    if (DEBUG) { // If debugging, report loop, tone, beat, and duration
      Serial.print(i);
      Serial.print(":");
      Serial.print(beat);
      Serial.print(" ");    
      Serial.print(tone);
      Serial.print(" ");
      Serial.println(duration);
    }
  }
}

/*
 * NOTES
 * The program purports to hold a tone for 'duration' microseconds.
 *  Lies lies lies! It holds for at least 'duration' microseconds, _plus_
 *  any overhead created by incremeting elapsed_time (could be in excess of 
 *  3K microseconds) _plus_ overhead of looping and two digitalWrites()
 *  
 * As a result, a tone of 'duration' plays much more slowly than a rest
 *  of 'duration.' rest_count creates a loop variable to bring 'rest' beats 
 *  in line with 'tone' beats of the same length. 
 * 
 * rest_count will be affected by chip architecture and speed, as well as 
 *  overhead from any program mods. Past behavior is no guarantee of future 
 *  performance. Your mileage may vary. Light fuse and get away.
 *  
 * This could use a number of enhancements:
 * ADD code to let the programmer specify how many times the melody should
 *     loop before stopping
 * ADD another octave
 * MOVE tempo, pause, and rest_count to #define statements
 * RE-WRITE to include volume, using analogWrite, as with the second program at
 *          http://www.arduino.cc/en/Tutorial/PlayMelody
 * ADD code to make the tempo settable by pot or other input device
 * ADD code to take tempo or volume settable by serial communication 
 *          (Requires 0005 or higher.)
 * ADD code to create a tone offset (higer or lower) through pot etc
 * REPLACE random melody with opening bars to 'Smoke on the Water'
 */

... i ara controlem el volum

això no ha sortit, no veig l'efecte PWM... mirar...

Com que el pin 9 és PWM, podem utilitzar aquest fet per controlar el volum. Es tracta de què en el pin 9 podem tenir tot el rang de tensions entre 0 i 5V, no només els valors LOW i HIGH. Això es traduirà en que l'amplada del pols canviarà. Informació sobre PWM a http://webzone.k3.mah.se/k3dacu/projects/ivrea/motor/pwm.html.

Utilitzem analogWrite en comptes de digitalWrite:

/* Play Melody
 * -----------
 *
 * Program to play melodies stored in an array, it requires to know
 * about timing issues and about how to play tones.
 *
 * The calculation of the tones is made following the mathematical
 * operation:
 *
 *       timeHigh = 1/(2 * toneFrequency) = period / 2
 *
 * where the different tones are described as in the table:
 *
 * note 	frequency 	period 	PW (timeHigh)	
 * c 	        261 Hz 	        3830 	1915 	
 * d 	        294 Hz 	        3400 	1700 	
 * e 	        329 Hz 	        3038 	1519 	
 * f 	        349 Hz 	        2864 	1432 	
 * g 	        392 Hz 	        2550 	1275 	
 * a 	        440 Hz 	        2272 	1136 	
 * b 	        493 Hz 	        2028	1014	
 * C	        523 Hz	        1912 	956
 *
 * (cleft) 2005 D. Cuartielles for K3
 */

int ledPin = 13;
int speakerOut = 9;               
byte names[] = {'c', 'd', 'e', 'f', 'g', 'a', 'b', 'C'};  
int tones[] = {1915, 1700, 1519, 1432, 1275, 1136, 1014, 956};
byte melody[] = "2d2a1f2c2d2a2d2c2f2d2a2c2d2a1f2c2d2a2a2g2p8p8p8p";
// count length: 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
//                                10                  20                  30
int count = 0;
int count2 = 0;
int count3 = 0;
int MAX_COUNT = 24;
int statePin = LOW;

void setup() {
 pinMode(ledPin, OUTPUT); 
}

void loop() {
  analogWrite(speakerOut, 0);     
  for (count = 0; count < MAX_COUNT; count++) {
    statePin = !statePin;
    digitalWrite(ledPin, statePin);
    for (count3 = 0; count3 <= (melody[count*2] - 48) * 30; count3++) {
      for (count2=0;count2<8;count2++) {
        if (names[count2] == melody[count*2 + 1]) {       
          analogWrite(speakerOut,500);
          delayMicroseconds(tones[count2]);
          analogWrite(speakerOut, 0);
          delayMicroseconds(tones[count2]);
        } 
        if (melody[count*2 + 1] == 'p') {
          // make a pause of a certain size
          analogWrite(speakerOut, 0);
          delayMicroseconds(500);
        }
      }
    }
  }
}

Joguines musicals: produir audio

Per produir audio hi ha diferents possibilitats. Una possibilitat és utilitzar una placa arduino feta exprès:

El Adafruit Wave Shield for Arduino Kit val 22$ (hi ha un video que utilitza aquesta placa)

Ara bé, hi ha una manera més bàsica i més universal: utilitzar directament els microcontroladors i PWM: *http://www.rpi.edu/~kouttd/03/Rage_against_the_arduino.html

Es basa en convertir un fitxer wav a un fitxer de text. Aquest fitxer s'envia al microcontrolador, i és capaç de sintetitzar la veu.

Algú ha fet audio amb la tècnica PWM sobre el Arduino: http://www.arduino.cc/playground/Code/PCMAudio

En aquest exemple, per convertir un wav a txt s'utilitza l'aplicatiu wav2c

wav2c macstartup-cut.wav sounddata.h sounddata

Vull fer el projecte i el codi explicat a http://www.arduino.cc/playground/Code/PCMAudio. El fitxer sounddata.h el fico dins de ~/Escritorio/arduino-0017/hardware/libraries, on creo la carpeta sounddata. D'aquesta manera la llibreria s'importa bé i el codi original compila sense problemes..

projecte pcm_audio

En el Arduino Mega que faig servir també hi ha una sortida 13 que és PWM, i per tant el codi no el canvio. Aquí és on ficaré el speaker, amb una resistència de 1K (es pot veure un dibuix a http://itp.nyu.edu/physcomp/Labs/ToneOutput)

El LED que en el codi posa 13, jo el col.locaré al pin digital 53 tal com havia fet en el primer exemple. No veig massa bé per què serveix aquest LED.

El codi compila bé, però no sento res. Clar que potse el problema és el speaker que té massa impedància (el projecte hello_world_audio funciona però el speaker se sent extremadament fluix).

compila bé però de moment no s'escolta res...

xips ISD

aquests xips (per exemple ISD1016, són 16 segons) és la solució amb què es fan les joguines musicals. Per exemple, hi ha kits de CEBEK de reproducció/playback que es basen en aquests xips. El problema és que els xips estan obsolets per al públic en general. L'avantatge és que és fàcil d'introduir el so i després reproduir-lo. (veure a 50 ways..., Audio PCM, Notes2).

Arduino i MIDI

és fàcil fer un theremin amb sensors de ultrasons. S'ha de basar en http://www.youtube.com/watch?v=8wnQbG10RcM, però en comptes d'enviar el senyal al piezo, s'ha de composar un missatge midi a la targeta de so. Aleshores amb el fluidsynth ja puc utilitar un soundfont de midi.

Així doncs, amb Arduino puc recuperar tot el que feia de bateria electrònica, i ja puc substituir tota la part del ALESIS DM5 per un controladro MIDI fet amb Arduino.

llibreria ttymidi

Em descarrego el tar de l'enllaç. Dins la carpeta hi ha dues coses: la llibreria per a l'arduino; i l'aplicació ttymidi que s'ha de compilar:

The ttyMIDI source code is comprised of a single C file.  To compile it, just
run the following command:

$ gcc ttymidi.c -o ttymidi -lasound

This program depends on libasound2, so you should have the development headers
for that installed. In Debian or Ubuntu, you can install it by running:

$ apt-get install libasound-dev

After you compile ttyMIDI, you may wish to copy it to /usr/bin for easy access.

1. Carrego en el Arduino l'aplicació d'exemple (ardumidi_test.pde), després d'instal.lar les llibreries (s'ha de reiniciar el SDK d'Arduino).

2. Ara ja puc executar ttymidi:

$ ./ttymidi -s /dev/ttyUSB0 -v
0x80 Note off           000 060 127
0x80 Note off           000 075 127
0x80 Note off           000 079 127
0x90 Note on            000 060 127
0x90 Note on            000 075 127
0x90 Note on            000 079 127
0x80 Note off           000 060 127
0x80 Note off           000 075 127
0x80 Note off           000 079 127
...

en l'executable es pot especificar els bauds (ttymidi -b 115200), però el valor 115200 és el que hi ha per defecte, i evidentment ha de coincidir amb els bauds del meu sketch.

Veig que el arudino m'està enviant missatges MIDI, però de moment no sona res. El bo del cas és que ttymidi és una aplicació ALSA i la puc connectar al fluidsynth (o a l'aplicació que jo vulgui).

La solució JACK:

$ fluidsynth -a jack /home/joan/soundfonts/AI-APiano02trans.sf2

i en el QJackctl connecto el ttymidi al fluidsynth (i en la pestanya d'audio el fluidsynth al System)

i la solució purament ALSA:

$ fluidsynth -a alsa -m alsa_seq -l /home/joan/soundfonts/AI-APiano02trans.sf2

i amb el aconnectgui connecto el ttymidi al fluidsynth

Arduino per fer instruments controladors

En aquest exemple l'Arduino serveix per moure les baquetes que faran sonar un xilòfon de joguina:


creat per Joan Quintana Compte, octubre 2009

Eines de l'usuari
Espais de noms
Variants
Accions
Navegació
IES Jaume Balmes
Màquines recreatives
CNC
Informàtica musical
joanillo.org Planet
Eines