Simple Music Maker

Enter Your Electronics & Design Project for Your Chance to Win a Fog Machine and a $100 Shopping Cart!

Back to The Project14 homepage

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