15

I can make monophonic sounds by toggling a single pin (at a varying rate) connected to a piezo buzzer.

How can I generate two mixed audio signals in software to create polyphony?

Here's the code I'm using to play a simple tune.

#define F_CPU 8000000UL // 8MHz
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/delay.h>

// number of timer0 overflows/sec
#define INT_PER_SEC 31250

// Frequencies (in Hz) of notes
#define F_FSH_4 370
#define F_A_4 440
#define F_B_4 494
#define F_E_4 330
#define F_CSH_5 554
#define F_D_5 587
#define F_FSH_5 740
#define F_CSH_4 277
#define F_GSH_4 415

// number of timer0 overflows for notes
#define REST -1 // special case
#define FSH_4 INT_PER_SEC/F_FSH_4
#define A_4 INT_PER_SEC/F_A_4
#define B_4 INT_PER_SEC/F_B_4
#define E_4 INT_PER_SEC/F_E_4
#define CSH_5 INT_PER_SEC/F_CSH_5
#define D_5 INT_PER_SEC/F_D_5
#define FSH_5 INT_PER_SEC/F_FSH_5
#define CSH_4 INT_PER_SEC/F_CSH_4
#define GSH_4 INT_PER_SEC/F_GSH_4

#define SEMIQUAVER_TIME 60  // ms
#define BREATH_TIME 20      // ms

volatile uint32_t intrs = 0;
volatile int32_t curNote = REST;

// TIMER0 overflow
ISR(TIMER0_OVF_vect)
{
    if (curNote == REST)
        intrs = 0;
    else
    {
        intrs++;
        if (intrs >= curNote)
        {
            PORTD ^= _BV(PD4);
            intrs = 0;
        }
    }
}


void play(int32_t note, uint32_t len)
{
    int i;
    curNote = note;
    for (i = 0; i< len; i++)
        _delay_ms(SEMIQUAVER_TIME);
    curNote = REST;
    _delay_ms(BREATH_TIME);
}

int main(void)
{
    /* setup clock divider. Timer0 overflows on counting to 256.
     * 8Mhz / 1 (CS0=1) = 8000000 increments/sec. Overflows every 256, so 31250
     * overflow interrupts/sec */
    TCCR0B |= _BV(CS00);

    // enable overflow interrupts
    TIMSK0 |= _BV(TOIE0);

    // PD4 as output
    DDRD = _BV(PD4);

    TCNT0 = 0;
    intrs = 0;

    curNote = REST;

    // enable interrupts
    sei();

    while (1)
    {
        // Axel F
        play(FSH_4, 2);
        play(REST, 2);
        play(A_4, 3);
        play(FSH_4, 2);
        play(FSH_4, 1);
        play(B_4, 2);
        play(FSH_4, 2);
        play(E_4, 2);
        play(FSH_4, 2);
        play(REST, 2);
        play(CSH_5, 3);
        play(FSH_4, 2);
        play(FSH_4, 1);
        play(D_5, 2);
        play(CSH_5, 2);
        play(A_4, 2);
        play(FSH_4, 2);
        play(CSH_5, 2);
        play(FSH_5, 2);
        play(FSH_4, 1);
        play(E_4, 2);
        play(E_4, 1);
        play(CSH_4, 2);
        play(GSH_4, 2);
        play(FSH_4, 6);
        play(REST, 12);
    }
}
endolith
  • 28,494
  • 23
  • 117
  • 181
Toby Jaffey
  • 28,796
  • 19
  • 96
  • 150

12 Answers12

8

Well one easy trick is to use two pins with PWM, and tie them to the opposite sides of the speaker. Then modulate each pin at a different speed, and you can play two notes at once...basically the speaker is mixing them together for you. More than two notes and you will have to do it in software as mentioned.

davr
  • 6,802
  • 2
  • 25
  • 37
  • 2
    If you're using PWM (switching at a much higher frequency than the desired signal), then you can already mix multiple signals together using only the one output pin. – endolith Apr 07 '10 at 14:53
5

The standard way of getting polyphony is to interrupt at some fixed interrupt rate (most often 8000 Hz or 44100 Hz), get a "high" (+1) or "low" (-1) (or something intermediate) from each sound source, add up all the numbers to get a total, then send that total number out the DAC.

As others have said here, with a bit of cleverness a high-speed PWM can replace a DAC.

The "microcontroller polyphony" page gives some more details and tips.

davidcary
  • 17,426
  • 11
  • 66
  • 115
3

I think this nice old little PC DOS game gem used real polyphonic sound through the PC speaker: Digger.

I don't know how they made it, but you can download the C source code from the site.

2

If you're using software to time your speaker events, the easiest approach is probably to generate two independent data streams and alternate between them. This approach can work pretty well whether the speaker output is controlled by an I/O pin or a DAC. For example:

int selector;
uint16_t phase[8],freq[8];

void interrupt(void) { selector++; selector&=7; phase[selector] + freq[selector]; DAC_OUT = sinewave[phase[selector] >> 8]; }

The above is the essential approach I used in a PIC-based music box in 1996 (using assembly code rather than C). Note that the interrupt rate has to be 8 times the effective sample rate, but each interrupt only has to do the processing for a single voice. Note that if the output filtering is good, this approach will yield 3 bits more of effective DAC resolution than would adding the samples numerically and then outputting them, but it will generate a lot of noise at the sample rate and multiples thereof. Filtering is thus even more important than it would be otherwise.
supercat
  • 45,939
  • 2
  • 84
  • 143
2

This might help -> simple PWM DAC

Jim
  • 3,325
  • 2
  • 28
  • 39
  • To get the Arduino to play the notes asynchronously you would need to use a system similar to MIDI - with separate commands for note on and note off etc, the built in tone library does this but does not do polyphony - but the latest version looks like it does - http://code.google.com/p/rogue-code/wiki/ToneLibraryDocumentation – Jim Apr 05 '10 at 18:59
2

you could simply add two square waves, and use fast pwm to output the "analog" signal to the speaker.

here's yet another different method if you like game sound, quick and dirty:

https://gitweb.bl0rg.net/cgi-bin/gitweb.cgi?p=arduinisten.git;a=blob;f=projekte/soundz/arpeggio/arpeggio.pde;h=6ceb64a57916c094e87e5983c07b5dd1b4623083;hb=HEAD

1

They used to do this on old game systems and in the days of "PC speakers", but I don't know how.

First guess: Think of the wave you would ideally make, then imagine distorting it into a heavily clipped square shape, then create that square shape by toggling your output at the appropriate times. Would have lots of intermodulation, though.

Second thought: Can you greatly increase the frequency of oscillation and output analog signals PWM-style?

endolith
  • 28,494
  • 23
  • 117
  • 181
  • 2
    I remember looking at an NES emulator a long time ago and I believe they used three waveforms each with a programmable frequency. Two square waves and one triangle wave. – mjh2007 Apr 05 '10 at 19:28
  • ...and one noise source, apparently. http://en.wikipedia.org/wiki/NES_Sound_Format – endolith Apr 07 '10 at 14:54
1

I believe there is a tone library for the Arduino that does two tones. You should be able to adapt the code to the AVR chip you are using. There are also a couple of excellent waveform generation threads at arduino.cc

If you decide to add a DAC I have a numerically controlled oscillator example at http://wiblocks.luciani.org/docs/app-notes/nb1a-nco.html Four independent output channels. The quad DAC and reference is only about $2 or so.

tyblu
  • 8,167
  • 6
  • 40
  • 70
jluciani
  • 11,646
  • 1
  • 34
  • 54
1

As has been mentioned you could do this the same way it used to be done with a PC speaker (which only supports on/off optionally attached to a PWM controller.) Basically my understanding of the method is that you switch the speaker on and off fast enough that it's never fully on or off (a bit like how a switch-mode power supply works.) This leaves the speaker constantly moving between on and off, generating an analogue signal.

The only gotchas are that you need a real speaker (I think a piezo moves so fast it reaches full on and full off too quickly) and you need to be able to toggle the bit fast enough. I did some experiments and came up with a maximum speed of around 5MHz which should be ample for an 11,025 Hz audio signal (probably the best quality you could hope to get.)

Of course 11025Hz @ 8-bit is 11 kilobytes/second, which is much faster than the speed of a serial port. It would only allow maybe a second or two worth of audio to be stored in the flash, so you're pretty much limited to playing audio generated on the fly, providing that leaves enough spare CPU time to twiddle the speaker!

There are a couple of other methods to achieve this too, and it looks like there's already an implementation for the Arduino of the method described above.

Malvineous
  • 1,649
  • 1
  • 15
  • 27
  • 2
    You can use a filter before the speaker to smooth out the PWM, regardless of how fast the speaker itself moves. – endolith Mar 27 '10 at 12:51
1

You can just add the streams as described here:

http://www.vttoth.com/digimix.htm

terrace
  • 1,334
  • 1
  • 15
  • 28
1

Play sound A for a moment, like maybe 50 ms then sound B and switch back and forth. The idea is to switch faster than the ear can tell and it will sound like both playing at the same time.

Matt Williamson
  • 1,192
  • 2
  • 10
  • 24
0

Here is my code for playing 2 tunes at the same time. Sorry, you have to register to AVR freaks to get access.

avra
  • 1,872
  • 3
  • 14
  • 21