new note player 32 bit mcu

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.


Cool. I'll definitely be interested once I finish my 8 bit projects. I ordered a Due and a few other 32 bit modules, and producing good sound is an interest of mine.

It seems like everyone just uses sampled sounds now. Not like the days when you could buy a chip that produces generated sounds.

:slight_smile: Having a lot of success with the code at the moment as it seems efficient and accurate (all integers now) and I got the synth data for William Tell Overture to play on it late last night.

It's a matter of having the correct interfaces to do the things people want to do.