Skip to main content
+ code to start or stop a note.
Source Link
Edgar Bonet
  • 45.2k
  • 4
  • 42
  • 81

Edit: I wrote “You can change the notes array any time to add, remove or change notes.” Here is how to start or stop a note, i.e. to add or remove it from the list of active notes:

// Start a note. Returns an index to be used with stop_note(), or -1 if
// we couldn't start the note because we are already outputting the
// maximum number of simultaneous notes.
int start_note(float frequency)
{
    int i;  // index of the note in the notes[] array

    // Find a free slot in the array.
    for (i = 0; i < MAX_NOTES; i++)
        if (!notes[i].is_active) break;  // found

    // Couldn't find a free slot?
    if (i == MAX_NOTES) return -1;

    // Setup the note parameters.
    notes[i].frequency = frequency;
    notes[i].phase = 0;
    notes[i].is_active = true;
    return i;
}

// Stop the note with the given index.
void stop_note(int index)
{
    if (index >=0 && index < MAX_NOTES)
        notes[index].is_active = false;
}

Edit: I wrote “You can change the notes array any time to add, remove or change notes.” Here is how to start or stop a note, i.e. to add or remove it from the list of active notes:

// Start a note. Returns an index to be used with stop_note(), or -1 if
// we couldn't start the note because we are already outputting the
// maximum number of simultaneous notes.
int start_note(float frequency)
{
    int i;  // index of the note in the notes[] array

    // Find a free slot in the array.
    for (i = 0; i < MAX_NOTES; i++)
        if (!notes[i].is_active) break;  // found

    // Couldn't find a free slot?
    if (i == MAX_NOTES) return -1;

    // Setup the note parameters.
    notes[i].frequency = frequency;
    notes[i].phase = 0;
    notes[i].is_active = true;
    return i;
}

// Stop the note with the given index.
void stop_note(int index)
{
    if (index >=0 && index < MAX_NOTES)
        notes[index].is_active = false;
}
Source Link
Edgar Bonet
  • 45.2k
  • 4
  • 42
  • 81

You just have to add the waveforms. For example, if you want to play pure tones (the simplest and most boring timbre), you generate a sine wave for each of them and add them together:

struct Note {
    float frequency;
    float phase;
    bool is_active;
} notes[MAX_NOTES];

void output_sample()
{
    static uint32_t previous_time;
    uint32_t now = micros();
    uint32_t elapsed_time = now - previous_time;
    previous_time = now;
    float output = 0;
    for (int i = 0; i < MAX_NOTES; i++) {
        Note *note = &notes[i];
        if (note->is_active) {
            output += cos(note->phase);
            note->phase += 2*M_PI*note->frequency*1e-6*elapsed_time;
            if (note->phase >= 2*M_PI) {
                note->phase -= 2*M_PI;
            }
        }
    }
    analogWrite(DAC_PIN, 2048 + round(output * (2047.0 / MAX_NOTES)));
}

The function output_sample() should be called as frequently as possible. You can change the notes array any time to add, remove or change notes.

There is some room for optimization here, like:

  • storing 2*M_PI*note->frequency*1e-6 as the “frequency” in order to save some calculations

  • using a wave table instead of the cos() function (I don't know how good is the Due at transcendental math)

  • replacing elapsed_time by a constant if you can manage to output the samples at a fixed rate.