Jouer du .wav avec Arduino

Et oui avec juste quelques lignes de code on peut envoyer un fichier wave sur le port com Arduino et le synthétiser en interne sur une seule pin.

Le format exigé est du mono 8 bits PCM (non codé) échantilloné à 11025 Hz. On peut utilisez le freeware Audacity pour convertir des MP3. Je vous mets ici des exemples tout fait : http://balbarie.com/arduino/waves

Un haut-parleur doit être branché sur la sortie 3 avec une résistance en série (j'ai mis 100 ohms). L'Arduino doit être configuré à 115200 bauds.

En invite de commande vous pouvez envoyer un fichier ainsi :

C:\Users\JLB\Desktop>mode com5 baud=115200 parity=n data=8 stop=1 to=off xon=off odsr=off octs=off dtr=off rts=off idsr=off
puis
C:\Users\JLB\Desktop>copy/b test01.wav com5

J'ai joint avec les exemple un petit .bat qui exécute les deux commandes (il vous faudra sans doute y changer le N° du port com) :
C:\Users\JLB\Desktop>play test01.wav

Enfin voici la fonction :

//-----------------------------------------------
void playWave()
{
unsigned long previousTime = 0;
unsigned long currentTime = 0;
byte buffer[128];
byte count = 0;
byte inP = 0;
byte ouT = 0;
byte *point;

  // Changement de l'horloge du timer 2
  point = 0;
  point+= 0xB1;
  *point = *point & 0xF8;
  *point = *point | 0x01;
  
  for (;;)
  {
    // Gestion d'un buffer FIFO
    if (Serial.available())
    {
      if (count < 128)
      {
      buffer[inP] = Serial.read();
      inP = inP++ & 0x80;
      count++;
      }
    }
    
    // Respect de l'échantillonage à 11025 Hz
    currentTime = micros();
    if (currentTime >= (previousTime + 90))
    {
      previousTime = currentTime;
      if (count > 0)
      {
        analogWrite(3, buffer[ouT]);
        ouT = ouT++ & 0x80;
        count--;
      }
    }
  }
}

JLB

Bonjour,

L'utilisation de micros() et analogWrite() pour faire de la lecture de sample audio PCM est une mauvaise idée ...
AnalogWrite() est une fonction très lente (en terme d'éxécution), et la fonction micros() est tout sauf précise.

La "vrai" solution consiste à utiliser deux timer, un en mode "Fast PWM" pour générer le son de maniére fluide, et un autre à 8KHz pour charger les échantillons sonore.
Un exemple est disponible sur le playground : http://arduino.cc/playground/Code/PCMAudio

De plus ceci est tout simplement immonde :

// Changement de l'horloge du timer 2
point = 0;
point+= 0xB1;
*point = *point & 0xF8;
*point = *point | 0x01;

Ce n'est absolument pas portable !

TCCR2B &= ~((1 << CS22) | (1 << CS21))
TCCR2B |= (1 << CS20);

Est beaucoup plus lisible et rend le code portable sur divers ATmega :wink:

Je sais bien mais on s'en fout ici puisque le débit est limité à 11025 Hz.

Le but était de faire un code très court et facilement lisible. C'est un exemple didactique pas une appli définitive.

Pour micros() c'est faux. Cette fonction est rapide (uniquement lecture d'un compteur, aucune init, aucun controle). De toute façon la précision de ce compteur est de 4 us pour un quartz 16 MHz.

analogWrite est lent mais on a le temps dans cet exemple. On travaille même à 11,025 KHz alors que dans ton exemple on est à 8KHz.

Pour l'écriture des registres j'ai du faire cela parce que cela ne fait qu'une semaine que je connais Arduino et je n'ai pas encore réussi à trouver la documentation sur les definitions des registres et n'ai vu que quelques exemples. Si tu sais où je peux les trouver je suis preneur.

JLB

jihelbi:
Pour l'écriture des registres j'ai du faire cela parce que cela ne fait qu'une semaine que je connais Arduino et je n'ai pas encore réussi à trouver la documentation sur les definitions des registres et n'ai vu que quelques exemples. Si tu sais où je peux les trouver je suis preneur.

JLB

bonjour jihelbi
Comme je suis curieux, mais aussi quelquefois un peu cossard, surtout le WE :grin:
je m'attendais à un .ino exemple directement compilable/exploitable.
Il est plus facile ensuite pour reutiliser/deriver de partir d'un "truc" de base qui fonctionne que l'inverse.
pourquoi ce choix de ne pas mettre un .ino (pde) compilable directement utilisable avec le serial et tes .Wav d'exemples ?

Finalement j'ai l'impression que l'implémentation Arduino utilise exactement les mêmes labels que ceux de la datasheet ATMega et propose même des masques pour chaque bit. Si c'est le cas j'ai vraiment perdu du temps à chercher ces définitions.

Peux tu me confimer ce point ?

JLB

Artouste, le .ino figure depuis le début avec les exemples de .wav à l'url donnée.

http://balbarie.com/arduino/waves

JLB

jihelbi:
Je sais bien mais on s'en fout ici puisque le débit est limité à 11025 Hz.

Certes, mais vouloir bien faire les choses même si ce n'est pas utile est toujours une bonne source de connaissances :wink:

jihelbi:
Pour micros() c'est faux. Cette fonction est rapide (uniquement lecture d'un compteur, aucune init, aucun controle). De toute façon la précision de ce compteur est de 4 us pour un quartz 16 MHz.

micros() pour maintenir une fréquence d'échantillonnage ça reste trés moyen, ça marche mais c'est pas trés propre.
De plus l'utilisation d'une interruption sur un timer rend le code beaucoup plus propre (avis perso).

jihelbi:
analogWrite est lent mais on a le temps dans cet exemple. On travaille même à 11,025 KHz alors que dans ton exemple on est à 8KHz.

8KHz c'est standard dans le domaine du sample PCM, vouloir faire plus avec des sample sur 8 bits ne donnera pas vraiment un meilleur rendu sonore.

jihelbi:
Pour l'écriture des registres j'ai du faire cela parce que cela ne fait qu'une semaine que je connais Arduino et je n'ai pas encore réussi à trouver la documentation sur les definitions des registres et n'ai vu que quelques exemples. Si tu sais où je peux les trouver je suis preneur.

La meilleur doc -> le datasheet constructeur : http://www.atmel.com/Images/doc8271.pdf
Quelque exemples basique : Arduino Reference - Arduino Reference
Sinon pour avoir de vrai exemples d'utilisation de registre -> avrfreaks, tu ne trouvera pas grand chose du côte arduino vu que c'est "trop poussé".

jihelbi:
Artouste, le .ino figure depuis le début avec les exemples de .wav à l'url donnée.

http://balbarie.com/arduino/waves

JLB

j'ai essayé de récupéré ça ce matin en buvant mon café 8)

les liens sur le .ino et le .bat me renvoie une erreur 404 :grin:
Le .bat j'ai reconstitué (ça je sais encore faire ! :slight_smile: )
les .wav (au moins le 1er , ) c'est ok

jihelbi:
Finalement j'ai l'impression que l'implémentation Arduino utilise exactement les mêmes labels que ceux de la datasheet ATMega et propose même des masques pour chaque bit. Si c'est le cas j'ai vraiment perdu du temps à chercher ces définitions.

Le "langage arduino" n'est rien d'autre qu'une surcouche au langage C/C++ pour AVR donc tous ce que tu peut faire en C/C++ avr et utilisable avec arduino :wink:

Tu peut même faire un programme 100% avr-c sans loop() ni setup(), avec juste main() via l'ide arduino :wink:

Bonjour
Merci jhelbi pour cet exemple.
Chaque méthode de 'jeu de fichier Wav' a ses avantages et ses inconvénients.....

Nous avions eu sur ce forum des échanges concernant la librairie PCMaudio dans les fils suivants :
http://arduino.cc/forum/index.php/topic,72025.0.html
http://arduino.cc/forum/index.php/topic,74439.0.html

Désolé de réinventer la poudre mais cela m'amuse. J'ai découvert cette petite carte il y a une semaine et j'en suis tombé amoureux.

Merci Skywodd pour les tuyaux. J'avais bien entendu l'intention d'écrire mon propre main() mais j'ai commencé par jouer un peu.

J'ai par ailleurs écrit un petit moniteur qui est disponible ici : http://balbarie.com/arduino/jlbmonitor.zip

jihelbi:
Désolé de réinventer la poudre mais cela m'amuse. J'ai découvert cette petite carte il y a une semaine et j'en suis tombé amoureux.

Oui , on se laisse vite prendre au jeu :grin:
finalement le rendu est tres acceptable, le code est simple et pour faire de l'annonce de base en utilisant une seule pin c'est intéressant.
étape suivante lire le(s) .wav à partir d'une sd card, je regarderais semaine prochaine...

... et puis quelqu'un qui apprécie (entre autres) P Desproges ne peut pas fondamentalement être mauvais 8)

bonjour
test rapide avec une sd (sur eth shield), le test05.wav de jihelbi
c'est fonctionnel , mais la restitution est "chevrotante" , je pense que cela est du au remplissage/vidage du buffer par la lecture de la sd.
mais pour un dimanche ça suffit ! :grin:

#include <SD.h>

// On the Ethernet Shield, CS is pin 4. Note that even if it's not
// used as the CS pin, the hardware CS pin (10 on most Arduino boards,
// 53 on the Mega) must be left as an output or the SD library
// functions will not work.
const int chipSelect = 4;



void setup()

{
 pinMode(10, OUTPUT);
  
}

void loop()
{
unsigned long previousTime = 0;
unsigned long currentTime = 0;
byte buffer[128];
byte count = 0;
byte inP = 0;
byte ouT = 0;
byte *point;

  // Changement de l'horloge du timer 2
  point = 0;
  point+= 0xB1;
  *point = *point & 0xF8;
  *point = *point | 0x01;
  
  if (!SD.begin(chipSelect)) {
    //Serial.println("Card failed, or not present");
    // don't do anything more:
    return;
  }
  File dataFile = SD.open("test05.wav");
  
  
  for (;;)
  {
    // Gestion d'un buffer FIFO
   
      if (count < 128)
      {
      buffer[inP] = dataFile.read();
      inP = inP++ & 0x80;
      count++;
      }
    
    
    // Respect de l'échantillonage à 11025 Hz
    currentTime = micros();
    if (currentTime >= (previousTime + 90))
    {
      previousTime = currentTime;
      if (count > 0)
      {
        analogWrite(3, buffer[ouT]);
        ouT = ouT++ & 0x80;
        count--;
      }
    }
  }
}

Artouste j'ai la solution pour ta lecture sur SD.

Tout d'abord il y avait un bug dans ma gestion du buffer FIFO. je faisais (inP++ & 0x80) au lieu de faire (++inP & 0x80) et pareil pour ouT. Cela ne pouvait marcher qu'avec la fifo ne contenant qu'un seul octet.

Si tu réessayes maintenant cela devrait aller mieux.

Si cela ne suffit pas tu peux utiliser la lecture bufferisée de la librairie SD qui est annoncée beaucoup plus rapide. Pour cela utilise ce nouveau code fourni ci-dessous qui utilise une interruption périodique pour vider le buffer FIFO. Tu peux donc prendre ton temps pour remplir la FIFO après une lecture bufferisée de la carte.

byte buffer[128];
byte count = 0;
byte inP = 0;
byte ouT = 0;

void setup()
{
  // Init Timer 1 pour interruptions périodiques à 11025 Hz
  noInterrupts();
  TCCR1B = (TCCR1B & ~_BV(WGM13)) | _BV(WGM12);                  // Set CTC mode (Clear Timer on Compare Match)
  TCCR1A = TCCR1A & ~(_BV(WGM11) | _BV(WGM10));                  // ...
  TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);      // No prescaler
  OCR1A = 1451;                                                  // Set the compare register (16e6 Hz / 11025 Hz = 1451)
  TIMSK1 |= _BV(OCIE1A);                                         // Enable interrupt when TCNT1 == OCR1A 
  interrupts();

  // Changement de l'horloge du timer 2 pour du 31250 Hz à la place du 488,28125 Hz Arduino
  TCCR2B &= 0xF8; 
  TCCR2B |= 0x01;

  analogWrite(3, 1);        // Init du PWM par Arduino (pourquoi se compliquer la vie)
  OCR2B = 0;                // PWM à 0 surtout pas par analogWrite(3, 0) qui dévaliderait le PWM
  
  Serial.begin(115200);
}

ISR(TIMER1_COMPA_vect)
{
  if (count)
  {
    OCR2B = buffer[ouT];
    ouT = ++ouT & 0x80;
    count--;
  }
}

void loop()
{
  for (;;)
  {
    // Gestion d'un buffer FIFO
    if (Serial.available())
    {
      noInterrupts();
      buffer[inP] = UDR0;
      inP = ++inP & 0x80;
      count++;
      interrupts();
    }
  }
}

La version avec tous les bugs corrigés est ici :

http://balbarie.com/arduino/waves

et ici :

byte buffer[128];
byte inP = 0;
byte ouT = 0;
byte count = 0;

void setup()
{
  // Init Timer 1 pour interruptions périodiques à 11025 Hz
  noInterrupts();
  TCCR1B = (TCCR1B & ~_BV(WGM13)) | _BV(WGM12);                  // Set CTC mode (Clear Timer on Compare Match)
  TCCR1A = TCCR1A & ~(_BV(WGM11) | _BV(WGM10));                  // ...
  TCCR1B = (TCCR1B & ~(_BV(CS12) | _BV(CS11))) | _BV(CS10);      // No prescaler
  OCR1A = 1451;                                                  // Set the compare register (16e6 Hz / 11025 Hz = 1451)
  TIMSK1 |= _BV(OCIE1A);                                         // Enable interrupt when TCNT1 == OCR1A 
  interrupts();

  // Changement de l'horloge du timer 2 pour du 31250 Hz à la place du 488,28125 Hz Arduino
  TCCR2B &= 0xF8;           // Désolé pour les puristes masi sur tous les ATMega les bits prescaler sont cadrés à droite
  TCCR2B |= 0x01;           // ...

  analogWrite(3, 1);        // Init du PWM par Arduino (pourquoi se compliquer la vie)
  OCR2B = 0;                // PWM à 0 surtout pas par analogWrite(3, 0) qui dévaliderait le PWM
 
  Serial.begin(115200);
}

ISR(TIMER1_COMPA_vect)
{
  if (count)
  {
    OCR2B = buffer[ouT];
    ++ouT &= 0x7F;
    count--;
  }
}

void loop()
{
byte value;

  for (;;)
  {
    // Gestion d'un buffer FIFO
    if (Serial.available())
    {
      value = Serial.read();
      noInterrupts();
      if (count < 128)
      {
        buffer[inP] = value;
        ++inP &= 0x7F;
        count++;
      }
      interrupts();
    }
  }
}

JLB

jihelbi:
La version avec tous les bugs corrigés est ici :

http://balbarie.com/arduino/waves

Bonsoir Jhelbi
Merci, par pure et simple curiosité je regarderais demain. 8)
Avec humour et d'expérience, je n'aurais simplement jamais pu écrire cette phrase :grin:

Ce n'était qu'un euphémisme... L'informatique en elle même n'est finalement due qu'à un bug de la matière où quelques trous (appelées pompeusement zones déplétives) deviennent boulimiques d'électrons... ou pas... c'est selon...

jihelbi:
Ce n'était qu'un euphémisme... L'informatique en elle même n'est finalement due qu'à un bug de la matière où quelques trous (appelées pompeusement zones déplétives) deviennent boulimiques d'électrons... ou pas... c'est selon...

Que quoi !? Oula ça va trop loin pour moi :sweat_smile: