DDS Audio - preview of a toolkit I've been working on

Hi,
I recently completed building my Dr.Who Sonic Screwdriver, have a look at the video:

Since I spent some time on the audio and 'NeoPixel' LED effects, I thought I'd tidy up the routines and make 'em generally available - so I've banged in a class as the API for the audio and I'll get around to issuing the LED driver soon.

DDS_Audio generates can play tunes, tones, WAV samples and samples generated by "custom sample generators". It can also play more than one thing at once. An important point is that it can generate audio and drive 'NeoPixel' LEDs at the same time.

The attached complete sketch is a preview of the DDS_Audio player in action (no NeoPixels yet)

It runs on an Arduino Nano or Pro Mini, PCM audio out on D9.

If anyone can run this up, have a play, report any bugs/ideas/comments etc. - I'd be grateful

Yours,
TonyWilk

P.S. In my sonic, the mainline code reads a Flora 9dof accelerometer and modifies the sample generator parameters - rather than playing a fixed WAV loop.

P.P.S. I don't think the code is for beginners.

P.P.P.S I should give credit to this page: Advanced Arduino Sound Synthesis for the initial idea.

DDS_Audio_test_0v1.zip (33.9 KB)

This is good stuff. Thank you for doing this work, and making it available.

I would like to use this, but I'm also interested in polyphonic. It looks like this system was designed with polyphonic capabilities in mind. I plan to dig in and figure out what I need, but there are a few concepts I don't fully understand yet. Maybe you could save me some time and point me to where to start?

Jimmus:
I plan to dig in and figure out what I need, but there are a few concepts I don't fully understand yet.

There is this comment in dds_audio.cpp which basically describes how it works:

//-------------------------------------------------------------------------------------
//                                  Sound Generation
//-------------------------------------------------------------------------------------
// - uses simple Direct Digital Synthesis to generate the waveform at c. 31.250KHz
// - Notes on how DDS works:
//   There's a table of 256 sine values in sinetable[]
//   If we have an index into the wave table and, say, add 1 to it every time, we end up
//   extracting a sequence of values which is a sine wave.
//   Since we are doing this at a fixed frequency - the rate of the Timer2 interrupt
//   which is every 32uS - then by adding 1 to the index we would take 256 * 32uS
//   to generate one complete sine wave. This would be a total of 8,193uS which would be
//   an audio frequency of 122Hz.
//   To generate other frequencies, we make bigger steps in the wave table index.
//   For example, if we added 32 every time, we would step 0,32,64,96,128,160,192,224,0 (wraps at 256)
//   so it only takes 8 * 32uS = 256uS to output an approximation of a sine wave at 3,906Hz
//   This is a bit of a crap sinewave output with only 8 steps in it, but we are quite limited
//   in this processor only running at 16MHz. However, the result is not too bad when we filter
//   the output a bit.
//   Frequency resolution is helped by using a 16-bit phase value, the sinetable[] table is indexed
//   by the upper 8 bits. So to make 122Hz we would add 256 to phase for each sample.
//
// - Timer 2 ISR is called 31,250 times per second, it writes an 8-bit sample to the PWM output
// - samples are generated by the currently-selected sample function via the function pointer: pSampleFunc

I don't have any "help" yet on the API other than the comments in the header file.

However; in the main DDS_Audio_test.ino sketch there are a number of example 'generators', each generator supplies one audio sample at a time. They are called 32,000 times/second.

The simplest is:

//-----------------------------------------------
// Example generator 1
// - simply generates samples for a 1KHz sinewave
//
uint16_t g1_phase_add= Hz_TO_PHASE_STEP(1000);

uint8_t exampleGenerator1()
{
  static uint16_t phase;

    phase+= g1_phase_add;           // bump the phase
    return PHASE_TO_SINE(phase);    // return point in sinewave
}

which generates a 1kHz tone. It does this by using the value of 'phase' to step thru a sinewave table at a calculated rate - the size of 'phase_add' determines how fast 'phase' adds up and therefore how fast the sinewave table is stepped through. The faster, the higher the frequency.

One generator could generate, say, 4 separate tones and add them together - so it would be polyphonic (much more than 4 and the processor can't keep up).
e.g.

    phase1+= phase1_add;                 // bump the phase
    sine1= PHASE_TO_SINE(phase1);   // return point in sinewave

    phase2+= phase2_add;    
    sine2= PHASE_TO_SINE(phase2);  

    phase3+= phase3_add;    
    sine3= PHASE_TO_SINE(phase3);  

    phase4+= phase4_add;    
    sine4= PHASE_TO_SINE(phase4);  

// some magic to add them all together:

    return (sine1 >> 2) + (sine2 >> 2) + (sine3 >>2) + (sine4 >>2);

The values of each of the 'phase_add' values can be set in the main loop by: e.g.

phase1_add= Hz_TO_PHASE_STEP( 1200 );    // set for 1200Hz

or

phase1_add= NOTE_C5;      // set note C5  (the notes are already defined in 'phase step' form

To actually play something polyphonic will need some fairly complicated additional code in the main loop to time when each note should start and finish.

Hope that makes some sense... if you have any specific questions, just ask.

Yours,
TonyWilk

Impressive,
It would become awsome if I could put screwdriver bits in the top and really drive a screw with it.

some ideas that pop up:

  • the pitch of the sound feedback would indicate the torque
  • different bits could give different sounds
  • light at top
  • detection of high voltage wires indication
  • battery indication
  • ...

that said, very well done!