Enter Your Electronics & Design Project for Your Chance to Win a Fog Machine and a $100 Shopping Cart! | Project14 Home | |
Monthly Themes | ||
Monthly Theme Poll |
Introduction
This is a continuation of A Simple Arduino Music Box that I did for Project14.
The waveform for the note produced is held in a table in the Arduino's memory. That waveform is a description of the wave in the time
domain, ie what you'd see on an oscilloscope. Originally, for simplicity, I made that waveform a sine wave but there's no reason why it
shouldn't be something more complicated.
Although there are various methods we could use to describe the way that the shape of a waveform develops over time, a better approach for
sounds is to synthesize the waveform by adding together sine waves of different frequencies. That, then, will be a description in the
frequency domain similar to what you'd see on a spectrum analyser (we'll be doing the reverse of what the spectrum analyser does - the
analyser determines the frequencies that make up the waveform, we'll be starting with the frequencies and putting them back together to
make the waveform). Those frequencies will need to be integer multiples of the fundamental frequency, otherwise the wave won't end at the
same level as it starts and there will be a discontinuity where they join when we put them end to end. Frequencies that have an integer
relationship to a fundamental are called harmonics. (Note that the fundamental is the first harmonic, so the frequency that is twice the
fundamental is the second harmonic, and so on.)
The program
Here's a program to do that. It's written in C and was compiled with Visual C++ 2010. I imagine any C or C++ compiler going would be able
to do the same. Bear in mind that I'm a hardware enginer, so don't take it as an example of good programming technique - I know that it
isn't, but it does the job and it doesn't need to be at all efficient given the memory resources of a PC and the speed at which the processor
runs.
The includes at the start are meant to be:
#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <dos.h>
#include <errno.h>
#include <math.h>
/****************************************************************/ /* sw-wave-table.c */ /* Creates waveform by piecing together sine waves. */ /* Ends up with 512 values in int array that are */ /* ready to paste into Arduino sketch. */ /* Output file is called waveTable.txt */ /* Created with Visual C++ 2010, but should compile */ /* with any c++ compiler. */ /* 18th May 2018 Jon Clift */ /*--------------------------------------------------------------*/ /* Free to use however you like. No guarantee that it does */ /* anything useful and no support. */ /*--------------------------------------------------------------*/ /* Rev Date Comments */ /* 1.0 18/05/18 */ /****************************************************************/ #include #include #include #include #include #include // variables FILE *handle; FILE *handle2; char temp_string[256]; unsigned int waveTable[512]; float floatWaveTable[512]; // this defines the waveform // first value is amplitude of fundamental (normally, just set to 1.0) // that's followed by amplitudes of harmonics 2 to 10 // no explicit phase information - assumes all start in phase (which is ok for audio) //float harmonicTable[10] = {1.0,0.0,0.9,0.0,0.8,0.0,0.0,0.0,0.0,0.0}; float harmonicTable[10] = {1.0,0.9,0.8,0.7,0.6,0.5,0.4,0.3,0.0,0.0}; float scaleFactor = 1.0; float maxValue = 0.0; // main routine void main(int argc,char *argv[]) { int i,j; /* print banner */ printf("\n--- waveTable DOS UTILITY PROGRAM V1.0 ---\n"); printf("Builds wave table for Arduino Music Box blog.\n"); /* generate float wave table in array */ for(i=0;i<512;i++) { floatWaveTable[i]= 0.0; } for(j=0;j<10;j++) { for(i=0;i<512;i++) { floatWaveTable[i] = floatWaveTable[i] + ((sin(((j+1) * 2.0 * 3.1415926/512.0) * i)) * harmonicTable[j]); } } /* find maximum value and determine scale factor */ for(i=0;i<512;i++) { if(floatWaveTable[i] > maxValue) maxValue = floatWaveTable[i]; } scaleFactor = 32767.0 / maxValue; /* convert to int wave table in array */ for(i=0;i<512;i++) { waveTable[i] = (unsigned int) ((floatWaveTable[i] * scaleFactor) + 32768); } /* open output file */ if((handle=fopen("waveTable.txt","wt"))==NULL) { printf("Failed to open output file.\n"); _fcloseall(); } else { /* write file banner */ fprintf(handle,"//\n"); fprintf(handle,"// --- Wave table - generated by waveTable.exe \n"); fprintf(handle,"//\n"); fprintf(handle,"unsigned int waveTable[512] = {"); /* write table to file */ fprintf(handle,"\n"); for (i=0;i<32;i++) { fprintf(handle," "); for(j=0;j<16;j++) { fprintf(handle,"0x%04x",waveTable[(i*16) + j]); if(j<15) fprintf(handle,","); else { if(i==31) fprintf(handle,"};\n"); else fprintf(handle,",\n"); } } } /* close output file */ fclose(handle); /* open output .csv file */ if((handle2=fopen("waveTable.csv","wt"))==NULL) { printf("Failed to open .csv output file.\n"); _fcloseall(); } else { /* write table to file */ for (i=0;i<512;i++) { fprintf(handle2,"%i,%i,\n",i,waveTable[i]); } } /* close output file */ fclose(handle2); printf("Done.\n"); } }
The amplitudes of the first 10 harmonics are held in a table. The program constructs the wave table in the form of floating point values
by scanning across the wave table once for each harmonic adding the computed sine values for that harmonic to what is already there. Once
that is done, it finds the largest value in the wave table and then produces an unsigned int table which is appropriately scaled from the
floating point one.
The output from the table is a text file that I then paste into the Arduino sketch (an alternative method would have been to output a
header file that could be included in the sketch). I've changed the name of the table to 'waveTable' as that seemed a better description
than 'sineTable', so references to sineTable in the Arduino sketch will need to be changed.
The program also outputs a .csv file that can be read into a spreadsheet program and used to view the resulting waveform.
A Couple of Examples
First example: fundamental and just the 3rd and 5th harmonics. This will be fairly mellow.
float harmonicTable[10] = {1.0,0.0,0.9,0.0,0.8,0.0,0.0,0.0,0.0,0.0};
That produces this waveform in the table
looks like this at the loudspeaker terminals
and sounds like this
Second example: fundamental and lots of harmonics, gently tapering off. This will be harsher as a sound.
float harmonicTable[10] = {1.0,0.9,0.8,0.7,0.6,0.5,0.4,0.3,0.0,0.0};
That produces this waveform
looks like this at the loudspeaker terminals
and sounds like this
Something that's surprised me in all this is just what an effect the abrupt transitions at the start and end of the note have on the
quality. They completely wreck the intended sound giving a really objectionable clicking/slapping noise. Definitely need that envelope generator.
Next blog: Simple Arduino Music Box: Chords
Top Comments