Finally got around to writing code to play notes etc from the Due which is working great!
The code at the moment has a #define up the top which allows me to compile on the Due or on Windows for faster testing and debugging. If in windows it creates a few files I can view in an audio editor to verify and listen to.
At the moment it has the following features :-
- define an envelope for playback (ie. attack, decay, sustain and release)
- play a note of various frequencies for a particular envelope
- playback waveform can be sine, triangle, square or saw (quite standard)
- sample rate of end result can be input depending on needs (22050 works well)
- when playing it has a start and end frequency and will transition from start to end along the envelope
Have to add a few more things like phase shift, invert and note volume.
The first iteration was "rough" as it used a lot of floats for most calculations. I have since got rid of most except for initial parameter passing.
Internally it now uses all fixed point which is heaps faster and seems to still produce accurate output files.
I utilize a 1024 element lookup for sine which has the values for 0 - PI/2 as the sine function uses symmetry to determine the quadrant and return the correct value.
I want to eventually revisit a MOD player I wrote years ago and make it so the envelopes can define instruments that can then be played and mixed mod style. Hopefully it can handle 4 tracks in real-time.
Some bits of code...
// basic attack, decay, sustain and release env
void cEnv::set(int sample_rate, float time_scale, float attack_secs, float attack_amp, float decay_secs, float decay_amp, float sustain_secs, float sustain_amp, float release_secs)
{
clear(sample_rate, float2fixed(time_scale));
addNodeT(0.0f, 0.0f);
addNodeT(attack_secs * time_scale, attack_amp);
addNodeT((attack_secs + decay_secs) * time_scale, decay_amp);
addNodeT((attack_secs + decay_secs + sustain_secs) * time_scale, sustain_amp);
addNodeT((attack_secs + decay_secs + sustain_secs + release_secs) * time_scale, 0.0f);
}
void cNote::update(void) // Main guts ... for a point in time uses the envelope and note parameters to determine... a single value to be played
{
if (!m_update)
{
return;
}
fixed32_t env; // -1 to +1 ... wave is scaled to this value
for (int node = 1; node < m_env->m_nodes; node++) // need to fine out what nodes current time is between
{
if (m_t < m_env->m_arr_node_pos[node])
{
env = m_env->m_arr_node_amp[node - 1] - muldiv((m_t - m_env->m_arr_node_pos[node - 1]), m_env->m_arr_d[node], m_env->m_arr_t[node]); // checked
break; // from for
}
}
// see if frequency needs to interpolate between start and end
int l_freq;
if (m_freq_start == m_freq_end)
{
l_freq = m_freq_start; // just a single frequency... easy
}
else // between
{
l_freq = m_freq_start + muldiv((m_freq_end - m_freq_start), m_t, m_env->m_arr_node_pos[m_env->m_nodes - 1]); // linear stretcher
}
// need to determine where in the cycle we are and it depends on the sample rate, current frequency and the position in time
int r = muldiv(l_freq, m_t, m_env->m_sample_rate) % FMAG; // just fractional part 0.0 -> 1.0 (0 - FMAG returned which represents 0 - 2*PI)
int val;
if (m_env->m_shape <= 1) // sine
{
int fixedsin;
if (m_env->m_shape == 1) // square
{
if (r < (FMAG / 2))
{
val = fixed2int(2000 * env);
}
else
{
val = fixed2int(-2000 * env);
}
}
else // sin
{
fixedsin = fpsin(r / 4); // divide by 4 as function needs 0 - 4097
val = muldiv(2000 * env, fixedsin, FMAG * FMAG);
}
}
else if (m_env->m_shape == 2) // triangle
{
r = 2 * (r - (FMAG / 2));
val = muldiv(2000 * env, r, FMAG * FMAG);
}
else // saw
{
r = (abs(r - (FMAG / 2)) * 4) - FMAG;
val = muldiv(2000 * env, r, FMAG * FMAG);
}
// clip - in case we made a mistake... avoid blowing up DAC etc
if (val > 2000) { m_int_over++; val = 2000; }
if (val < -2000) { m_int_under++; val = -2000; }
m_sample = 2048 + val; // (int32_t)(2000.0f * env * sin(r * 2.0f * (float)PI));
m_t++; // one more tick each is 1/sample_rate seconds
m_update = 0; // ready ... need this if updating from interrupt
}
A few images of what gets rendered. First shows entire envelope and is "saw" pattern, the other is a partial envelope and is "sine" pattern.

