Simple synthesizer for STM32F401RE/STMstation.

Dependencies:   FastPWM mbed-dsp

This is a basic synthesizer to play music on the STM32F401RE Nucleo/STMstation development board:

/media/uploads/kkado/imgp1229.jpg

Please see the API documentation for further details.

Here's a demo of the synthesizer at work in a music composing program on the STMstation. This one is "Miku" by Anamanaguchi.

Revision:
0:c5ca205c0a80
Child:
1:db0c24aebb8a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/STMstation_synth.cpp	Mon Jul 03 08:16:42 2017 +0000
@@ -0,0 +1,284 @@
+#include "STMstation_synth.h"
+#include "mbed.h"
+#include "arm_math.h"
+#include "FastPWM.h"
+
+const float     freqs[] = {0,46.2493,48.9995,51.913,55,58.2705,61.7354,65.4064,69.2957,73.4162,77.7817,82.4069,87.3071,92.4986,97.9989,103.826,110,116.541,123.471,130.813,138.591,146.832,155.563,164.814,174.614,184.997,195.998,207.652,220,233.082,246.942,261.626,277.183,293.665,311.127,329.628,349.228,369.994,391.995,415.305,440,466.164,493.883,523.251,554.365,587.33,622.254,659.255,698.456,739.989,783.991,830.609,880,932.328,987.767,1046.5,1108.73,1174.66,1244.51,1318.51,1396.91,1479.98,1567.98,1661.22,1760,1864.66,1975.53,2093,2217.46,2349.32,2489.02,2637.02,2793.83,2959.96,3135.96,3322.44,3520,3729.31,3951.07,4186.01};
+const uint8_t   freqLength = sizeof(freqs)/sizeof(freqs[0]);
+const float     pi = 3.14159265359;
+
+//Constructor
+STMstation_synth::STMstation_synth():
+    //FSAMP(44100), period(0.000023),       //<<Only uncomment one of these lines!
+    //FSAMP(22050), period(0.000045),       //<<
+    FSAMP(11025), period(0.000091),         //<<
+    tone(AUDIO_PIN,1)
+{     
+    tone.pulsewidth_ticks(1);
+    tone.period_ticks(256);
+    sample.attach(this,&STMstation_synth::note2,period);
+}
+
+
+//Calculate the coefficients
+void STMstation_synth::calc_coefs(int i){  
+    Master.timbre[i] = Master.notes[i][Master.index[i]]/freqLength;
+    
+    uint8_t _freq_index = Master.notes[i][Master.index[i]] % freqLength;
+    float _freq = freqs[_freq_index];
+    uint32_t _duration = floor(0.5f + FSAMP*60.0f/Master.bpm[i]*(Master.durations[i][Master.index[i]]+1.0f)/16.0f);
+    uint8_t _attack = Master.AR[i][Master.index[i]] >> 4;
+    uint8_t _release = Master.AR[i][Master.index[i]] & 0b00001111;
+    
+    Master.freqIndex[i] = _freq_index;
+    Master.sineCoef[i] = 2*pi/FSAMP*_freq;
+    if(_freq == 0){Master.halfPeriod[i] = 0;}
+    else{Master.halfPeriod[i] = FSAMP/(2*_freq);}
+    Master.triSlope[i] = 2/Master.halfPeriod[i];
+    Master.envAtkEnd[i] = _attack/16.0f*_duration;
+    Master.envRelStart[i] = (1-_release/16.0f)*_duration;
+    if(_attack == 0){Master.atkSlope[i] = 0;}
+    else{Master.atkSlope[i] = 16.0f/(_attack*_duration);}
+    if(_release == 0){Master.relSlope[i] = 0;}
+    else{Master.relSlope[i] = 16.0f/(_release*_duration);}
+    Master.relOffset[i] = 16.0f/_release;
+    Master.volCoef[i] = Master.vol[i][Master.index[i]]/Master.vSum;
+}
+
+//Calculate vSum
+void STMstation_synth::calc_vSum2(){
+    uint8_t active = 0; //Number of active channels
+    uint16_t accum = 0; //Sum of volume channel values
+    bool over = 0;      //Is any value over 127?
+    for(int i=0; i<CHANNELS; i++){
+        if(Master.notes[i] != NULL){
+            active++;
+            accum += Master.vol[i][Master.index[i]];
+            if(Master.vol[i][Master.index[i]] > 127){over = 1;}
+        }
+    }
+    if(accum == 0)      {Master.vSum = 1;}
+    else if(over == 0)  {Master.vSum = active*127;}
+    else                {Master.vSum = accum;}
+}
+
+// Calculate the envelope
+void STMstation_synth::calc_env(){
+    for(int i=0; i<CHANNELS; i++){
+        if(Master.notes[i] != NULL){
+            if(Master.counter[i] < Master.envAtkEnd[i]){
+                if(Master.atkSlope == 0){Master.env[i] = 1;}
+                else{Master.env[i] = Master.atkSlope[i]*Master.counter[i];}
+            }
+            else if(Master.counter[i] > Master.envRelStart[i]){
+                if(Master.relSlope == 0){Master.env[i] = 1;}
+                else{Master.env[i] = Master.relOffset[i] - Master.relSlope[i]*Master.counter[i];}
+            }
+            else{
+                Master.env[i] = 1;
+            }
+        }
+    }
+}
+
+//Square wave switching function
+int8_t STMstation_synth::square(float _halfperiod, uint16_t _counter){
+    if(_halfperiod == 0){
+        return 0;;
+    }
+    
+    uint32_t _div = _counter/_halfperiod;
+    uint32_t _div2 = _div % 2;
+    if(_div2 == 0){
+        return -1;
+    }
+    else{
+        return 1;
+    }
+}
+
+//Triangle wave function
+void STMstation_synth::calc_triangle(uint8_t _channel, float _halfperiod, float _trislope, uint32_t _counter){
+    if(_halfperiod == 0){
+        Master.triVal[_channel] = 0;
+        return;
+    }
+    
+    int8_t _sign = 2*(uint32_t(_counter/_halfperiod) % 2) - 1;
+    Master.triVal[_channel] += _sign*_trislope;
+    if(_counter == 0){
+        Master.triVal[_channel] = 1;
+    }
+    if(Master.triVal[_channel] < -1){
+        Master.triVal[_channel] = -1;
+    }
+    else if(Master.triVal[_channel] > 1){
+        Master.triVal[_channel] = 1;
+    }
+}
+
+//Calculate noise
+void STMstation_synth::calc_noise(uint8_t _channel, uint8_t _freq, uint32_t _counter){
+    if(_freq == 0){
+        Master.noiseVal[_channel] = 0;
+        return;
+    }
+    
+    uint32_t _noisePeriod = (16-_freq);
+    if(_counter % _noisePeriod == 0){
+        Master.noiseVal[_channel] = 1-(rand() % 257)/128.0f;
+    }
+}
+
+//Calculate values
+void STMstation_synth::calc_val2(){
+    Master.val = 128;
+    for(int i=0; i<CHANNELS; i++){
+        if(Master.notes[i] != NULL){
+            if(Master.timbre[i] == 0){
+                Master.val += 127*arm_sin_f32(Master.sineCoef[i]*Master.counter[i])*Master.env[i]*Master.volCoef[i];
+            }
+            else if(Master.timbre[i] == 1){
+                Master.val += 127*square(Master.halfPeriod[i], Master.counter[i])*Master.env[i]*Master.volCoef[i];
+            }
+            else if(Master.timbre[i] == 2){
+                calc_triangle(i,Master.halfPeriod[i], Master.triSlope[i], Master.counter[i]);
+                Master.val += 127*Master.triVal[i]*Master.env[i]*Master.volCoef[i];
+            }
+            else if(Master.timbre[i] == 3){
+                calc_noise(i,Master.freqIndex[i],Master.counter[i]);
+                Master.val += 127*Master.noiseVal[i]*Master.env[i]*Master.volCoef[i];
+            }
+        }
+    }
+}
+
+//Remove channel
+void STMstation_synth::clear_channel(uint8_t _channel){
+    Master.notes[_channel] = NULL;
+    Master.durations[_channel] = NULL;
+    Master.AR[_channel] = NULL;
+    Master.vol[_channel] = NULL;
+    Master.max[_channel] = NULL;
+    Master.repeat[_channel] = NULL;
+    Master.counter[_channel] = NULL;
+    Master.index[_channel] = NULL;
+    *(Master.endptr[_channel]) = 1;
+}
+
+//Stop track
+void STMstation_synth::stop_track(melody &newMelody){
+    for(uint16_t i=0; i<CHANNELS; i++){
+        if(newMelody.notes[i] != NULL){
+            clear_channel(i);
+        }
+    }
+}
+
+//Check if track is playing, returns 0 if all channels ended, 1 otherwise
+bool STMstation_synth::check_track(melody &newMelody){
+    for(uint16_t i=0; i<CHANNELS; i++){
+        if(newMelody.ended[i] == 0){
+            return 1;
+        }
+    }
+    return 0;
+}
+
+//Check if notes are finished playing
+void STMstation_synth::check_end(){
+    uint32_t _duration;
+    for(int i=0; i<CHANNELS; i++){
+        _duration = floor(0.5f + FSAMP*60.0f/Master.bpm[i]*(Master.durations[i][Master.index[i]]+1.0f)/16.0f);
+        if(Master.counter[i] >= _duration){
+            if(Master.index[i] < Master.max[i]){
+                Master.index[i]++;
+            }
+            else{
+                if(Master.repeat[i] == 1){
+                    Master.index[i] = 0;
+                    *(Master.endptr[i]) = 1;
+                }
+                else{
+                    clear_channel(i);
+                    *(Master.endptr[i]) = 1;
+                }
+            }
+            Master.counter[i] = 0;
+        }
+        else{
+            Master.counter[i]++;
+        }
+    }
+}
+
+//Check if a new note is starting and if we need to recalculate coefficients
+void STMstation_synth::check_start(){
+    for(int i=0; i<CHANNELS; i++){
+        if(Master.counter[i] == 0){
+            calc_vSum2();
+            calc_coefs(i);
+        }
+    }
+}
+
+//Play dat funky music
+void STMstation_synth::note2(){
+    check_start();
+    calc_env();
+    calc_val2();
+    tone.pulsewidth_ticks(Master.val);
+    check_end();
+}
+
+//Start playing a tune
+void STMstation_synth::play(melody &newMelody, uint8_t refChannel, uint16_t newIndex){
+    uint32_t pos = 0;
+    uint32_t rem;
+    uint16_t chanIndex;
+    
+    //pc.printf("Playing track...\n");
+    //pc.printf("refChannel = %d, newIndex = %d\n", refChannel, newIndex);
+    
+    //Calculate how far into the track we're at, in terms of samples
+    for(uint16_t i=0; i<newIndex; i++){
+        pos += floor(0.5f + FSAMP*60.0f/newMelody.bpm*(newMelody.durations[refChannel][i]+1.0f)/16.0f);
+    }
+    
+    //pc.printf("pos = %d\n",pos);
+    
+    for(int i=0; i<CHANNELS; i++){
+        if(newMelody.notes[i] != NULL){
+            chanIndex = 0;
+            rem = pos;
+            for(uint16_t j=0; j<=newMelody.max[i]; j++){
+                if(rem < floor(0.5f + FSAMP*60.0f/newMelody.bpm*(newMelody.durations[i][j]+1.0f)/16.0f)){                
+                    *(Master.endptr[i]) = 1;                                            //Important! Overwrite the old .ended back to zero!
+                    Master.notes[i] = newMelody.notes[i];
+                    Master.durations[i] = newMelody.durations[i];
+                    Master.AR[i] = newMelody.AR[i];
+                    Master.vol[i] = newMelody.vol[i];
+                    Master.max[i] = newMelody.max[i];
+                    Master.repeat[i] = newMelody.repeat[i];
+                    Master.endptr[i] = &newMelody.ended[i];                             //Now we set the .ended pointer to the new track's
+                    *(Master.endptr[i]) = 0;                                            //Now we set the new track's .ended back to zero.
+                    Master.counter[i] = rem;
+                    Master.index[i] = chanIndex;
+                    Master.bpm[i] = newMelody.bpm;
+                    //pc.printf("Channel %d: chanIndex = %d, rem = %d\n",i,chanIndex,rem);
+                    break;
+                }
+                else{
+                    rem -= floor(0.5f + FSAMP*60.0f/newMelody.bpm*(newMelody.durations[i][j]+1.0f)/16.0f);
+                    chanIndex++;
+                }
+            }            
+        }
+    }
+    calc_vSum2();
+    for(uint16_t i=0; i<CHANNELS; i++){
+        if(Master.notes[i]!=NULL){
+            calc_coefs(i);
+        }
+    }
+}
\ No newline at end of file