Speech (& other audio) on the Pibrella

CAM00598A few weeks ago when I first got my pibrella one of the things I wanted to do was to try to get speech or a reasonable facsimile thereof from the buzzer but at that time I wasn’t able to put the time into getting it to work. however this evening my wife decided that she was going to hog the TV and the cats had decided that my desk was an anarchist squat & that I wasn’t going to be allowed to evict them so that i could do a bit of finger burning er I mean soldering and as the Raspberry Pi that is currently accessible when I can’t get to my desk or the living room is the one that the Pibrella is attached to i decided to revisit speech using the Pibrella piezo buzzer. Rather than creating speech the proper way by breaking the text into phonemes and outputting the phonemes i decided to cheat and use wav files and play those on the Pibrella’s piezo. I remembered that a few months ago when I was looking for something else I found a program for playing wav files on an Arduino which I had modified to to use wiringPi and some extra hardware to play wav files [I can’t remember where I found this file so apologies to the real author as my modifications are minimal and they deserve all the credit. When/If I remember where it came from i’ll update this post with their details] I made a few more modifications, mostly removing the addition I added and a tidy up of the wiringPi bits.

#include <stdio.h>
#include <stdlib.h>
#include <wiringPi.h>

#define PWM_PIN 22

typedef struct WavHeader_
{
 // Header information.
 char riff[4]; // RIFF
 int chunkSize; // Should be equal to: 4 + (8 + SubChunk1Size) + (8 + SubChunk2Size)
 char format[4]; // WAVE

 // Sub Chunk 1 information.
 char subChunk1Id[4]; // Contains "fmt "
 int subChunk1Size; // 16 for PCM.
 short int audioFormat; // PCM = 1
 short int channels; // Mono = 1, Stereo = 2
 int sampleRate; // 8000, 16000, 44100, etc
 int byteRate; // sampleRate * channels * bitsPerSample / 8
 short int blockAlign; // channels * bitsPerSample / 8
 short int bitsPerSample; // 8 bits = 8, 16 bits = 16

 // Sub chunk 2 information.
 char subChunk2Id[4]; // Contains "data"
 int subChunk2Size;
}WavHeader;

void WavPrintHeader(WavHeader *pHeader);
void WavPlay(WavHeader *pHeader, unsigned char *aData, int iDataLength);

void WavPrintHeader(WavHeader *pHeader)
{
 printf("\nWAV header Information:\n");
 printf("RIFF : %c%c%c%c\n", pHeader->riff[0], pHeader->riff[1], pHeader->riff[2], pHeader->riff[3]);
 printf("Chunk Size : %d\n", pHeader->chunkSize);
 printf("Format : %c%c%c%c\n", pHeader->format[0], pHeader->format[1], pHeader->format[2], pHeader->format[3]);

 printf("\nSub-Chunk 1 Information:\n");
 printf("Sub-Chunk 1 ID : %c%c%c%c\n", pHeader->subChunk1Id[0], pHeader->subChunk1Id[1], pHeader->subChunk1Id[2], pHeader->subChunk1Id[3]);
 printf("Sub-Chunk Size : %d\n", pHeader->subChunk1Size);
 printf("Audio Format : %d (PCM = 1)\n", pHeader->audioFormat);
 printf("Channels : %d (MONO = 1, STERIO = 2)\n", pHeader->channels);
 printf("Sampling Rate : %d Hz\n", pHeader->sampleRate);
 printf("Byte Rate : %d (%d)\n", pHeader->byteRate, (pHeader->sampleRate * pHeader->channels * pHeader->bitsPerSample) / 8);
 printf("block Alignment : %d (%d)\n", pHeader->blockAlign, (pHeader->channels * pHeader->bitsPerSample) / 8);
 printf("Bits Per Sample : %d bits\n", pHeader->bitsPerSample);

 printf("\nSub-Chunk 2 Information:\n");
 printf("Sub-Chunk 2 ID : %c%c%c%c\n", pHeader->subChunk2Id[0], pHeader->subChunk2Id[1], pHeader->subChunk2Id[2], pHeader->subChunk2Id[3]);
 printf("Sub-Chunk Size : %d\n", pHeader->subChunk2Size);

 printf("\nGeneral Information:\n");
 printf("Audio Length : %d seconds\n\n", pHeader->subChunk2Size / pHeader->byteRate);
}

void WavPlay(WavHeader *pHeader, unsigned char *aData, int iDataLength)
{
 unsigned char *pDataEnd = aData + iDataLength;

 // Initialize the library.
 if (wiringPiSetupGpio() != 0) { printf("Error initializing wiringPi library."); return; }

 // Set the PWM in information.
 pinMode(PWM_PIN, PWM_OUTPUT);
 pwmSetMode(PWM_MODE_BAL);

 // Set the rang to 256 since we only accept 8-bit PCM files.
 pwmSetRange(256);

 // Set the clock pin on PWM. It has to be at lest the sample rate, but we multiply by 10 for better quality.
 gpioClockSet (PWM_PIN, pHeader->sampleRate * 10);

 // This should be the same as the above but it isn't and I'm not sure way!!!!!
 //pwmSetClock( 19200000 / pHeader->sampleRate * 10);

 // We only support 8 bit per sample, so check for it.
 if (pHeader->bitsPerSample != 8) { printf("\nSorry, unsupported bits per sample value, only accept 8bits/sample.\n"); return; }

 // Loop through the data and modulate accordingly.
 while (aData < pDataEnd)
 {
 pwmWrite(1, *(aData++));
 delayMicroseconds( 1000000 / pHeader->sampleRate);
 }

 // Turn down the pin when done.
 pwmWrite(PWM_PIN, 0);
 pinMode(PWM_PIN, INPUT);
}


int main(int iArgumentCount,char *aArgumens[])
{
 FILE *pWavFile;
 WavHeader oWavHeader;

 unsigned char *aData; // Holds the PCM WAV data read from the file.
 int iDataLength;

 //Ensure we have enough arguments to start.
 if(iArgumentCount < 2) { printf("\nYou forgot a wave input file name...\n"); exit(1); }

 // Open file wave file, read the header first then data later.
 if( (pWavFile = fopen(aArgumens[1], "r")) == 0) { printf("Ops, unable to open the given wave file!\n"); exit(2); }

 // Read the header from the file.
 fread(&oWavHeader, sizeof(WavHeader), 1, pWavFile);
 WavPrintHeader(&oWavHeader);

 // Read the data.
 aData = malloc(oWavHeader.subChunk2Size);
 iDataLength = fread(aData, 1, oWavHeader.subChunk2Size, pWavFile);

 printf("\nRead data length from file: %d (%d)", iDataLength, oWavHeader.subChunk2Size);

 // Play the WAV data.
 WavPlay(&oWavHeader, aData, iDataLength);

 // Clean up and return.
 fclose(pWavFile);
 free(aData);

 return 0;
}

cut and paste the above into a file called playwav.c and compile it using

gcc -o playwav playwav.c -lwiringPi -lpthread

and when executed using sudo with an 8bit wav file a very very quite version of the wav file can be heard coming from the tinny Pibrella buzzer. My test file for this was a converted version of


This is ok for wav files I download or find elsewhere but it really isn’t text to speech where it converts on the fly so I needed to write a script to take some arbitrary text & speak convert it to a valid wav file for playwav to play on the Pibrella buzzer. I decided to use the google translate_tts API to convert some text to an audiofile, however as the tts API returns an mp3 I also needed to convert the mp3 to an 8bit wav file.

 

#!/bin/bash
tempfile=mktemp
TEST=$1
wget -U Mozilla -qO- "http://translate.google.com/translate_tts?ie=UTF-8&tl=en&q=$TEST" | mpg123 --wav tempfile  --8bit --rate 44000 --mono -
sudo ./wavpcm tempfile

[The line that starts wget sends the string to be converted to audio to the google translate_tts API and pipes the downloaded output (mp3) to mpg123 to convert it to an 8bit wav file that is saved in a temp file which is then output (very very quietly) on the Pibrella’s -buzzer in the next line.]

The text to be played is passed as an argument to the script. e.g.

./speak.sh "this is a test, this is only a test"

This really doesn’t have much practical use as the audio is much too quiet although as the Pibrella’s buzzer is on the same GPIO that the audio jack is you could plug in a speaker or headphones and get louder audio although again that does kind of defeat the whole purpose of what I was trying to achieve :)

Share

Leave a Reply

Your email address will not be published. Required fields are marked *

 

russelldavis.org © 2014 Frontier Theme