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.

Committer:
kkado
Date:
Mon Jul 03 08:35:31 2017 +0000
Revision:
1:db0c24aebb8a
Parent:
0:c5ca205c0a80
Corrected documentation placement.

Who changed what in which revision?

UserRevisionLine numberNew contents of line
kkado 0:c5ca205c0a80 1 #include "STMstation_synth.h"
kkado 0:c5ca205c0a80 2 #include "mbed.h"
kkado 0:c5ca205c0a80 3 #include "arm_math.h"
kkado 0:c5ca205c0a80 4 #include "FastPWM.h"
kkado 0:c5ca205c0a80 5
kkado 0:c5ca205c0a80 6 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};
kkado 0:c5ca205c0a80 7 const uint8_t freqLength = sizeof(freqs)/sizeof(freqs[0]);
kkado 0:c5ca205c0a80 8 const float pi = 3.14159265359;
kkado 0:c5ca205c0a80 9
kkado 1:db0c24aebb8a 10 //Sampling rate, uncomment one line only!
kkado 1:db0c24aebb8a 11 //const uint16_t FSAMP = 44100; const float period = 0.000023;
kkado 1:db0c24aebb8a 12 //const uint16_t FSAMP = 22050; const float period = 0.000045;
kkado 1:db0c24aebb8a 13 const uint16_t FSAMP = 11025; const float period = 0.000091;
kkado 1:db0c24aebb8a 14
kkado 0:c5ca205c0a80 15 //Constructor
kkado 1:db0c24aebb8a 16 STMstation_synth::STMstation_synth():tone(AUDIO_PIN,1)
kkado 0:c5ca205c0a80 17 {
kkado 1:db0c24aebb8a 18 begin();
kkado 0:c5ca205c0a80 19 }
kkado 0:c5ca205c0a80 20
kkado 1:db0c24aebb8a 21 //Constructor
kkado 1:db0c24aebb8a 22 STMstation_synth::STMstation_synth(PinName audio_pin):tone(audio_pin,1)
kkado 1:db0c24aebb8a 23 {
kkado 1:db0c24aebb8a 24 begin();
kkado 1:db0c24aebb8a 25 }
kkado 1:db0c24aebb8a 26
kkado 1:db0c24aebb8a 27 //Set up PWM and timer interrupt
kkado 1:db0c24aebb8a 28 void STMstation_synth::begin(){
kkado 1:db0c24aebb8a 29 tone.pulsewidth_ticks(128);
kkado 1:db0c24aebb8a 30 tone.period_ticks(256);
kkado 1:db0c24aebb8a 31 sample.attach(this,&STMstation_synth::note,period);
kkado 1:db0c24aebb8a 32 }
kkado 0:c5ca205c0a80 33
kkado 0:c5ca205c0a80 34 //Calculate the coefficients
kkado 0:c5ca205c0a80 35 void STMstation_synth::calc_coefs(int i){
kkado 0:c5ca205c0a80 36 Master.timbre[i] = Master.notes[i][Master.index[i]]/freqLength;
kkado 0:c5ca205c0a80 37
kkado 0:c5ca205c0a80 38 uint8_t _freq_index = Master.notes[i][Master.index[i]] % freqLength;
kkado 0:c5ca205c0a80 39 float _freq = freqs[_freq_index];
kkado 0:c5ca205c0a80 40 uint32_t _duration = floor(0.5f + FSAMP*60.0f/Master.bpm[i]*(Master.durations[i][Master.index[i]]+1.0f)/16.0f);
kkado 0:c5ca205c0a80 41 uint8_t _attack = Master.AR[i][Master.index[i]] >> 4;
kkado 0:c5ca205c0a80 42 uint8_t _release = Master.AR[i][Master.index[i]] & 0b00001111;
kkado 0:c5ca205c0a80 43
kkado 0:c5ca205c0a80 44 Master.freqIndex[i] = _freq_index;
kkado 0:c5ca205c0a80 45 Master.sineCoef[i] = 2*pi/FSAMP*_freq;
kkado 0:c5ca205c0a80 46 if(_freq == 0){Master.halfPeriod[i] = 0;}
kkado 0:c5ca205c0a80 47 else{Master.halfPeriod[i] = FSAMP/(2*_freq);}
kkado 0:c5ca205c0a80 48 Master.triSlope[i] = 2/Master.halfPeriod[i];
kkado 0:c5ca205c0a80 49 Master.envAtkEnd[i] = _attack/16.0f*_duration;
kkado 0:c5ca205c0a80 50 Master.envRelStart[i] = (1-_release/16.0f)*_duration;
kkado 0:c5ca205c0a80 51 if(_attack == 0){Master.atkSlope[i] = 0;}
kkado 0:c5ca205c0a80 52 else{Master.atkSlope[i] = 16.0f/(_attack*_duration);}
kkado 0:c5ca205c0a80 53 if(_release == 0){Master.relSlope[i] = 0;}
kkado 0:c5ca205c0a80 54 else{Master.relSlope[i] = 16.0f/(_release*_duration);}
kkado 0:c5ca205c0a80 55 Master.relOffset[i] = 16.0f/_release;
kkado 0:c5ca205c0a80 56 Master.volCoef[i] = Master.vol[i][Master.index[i]]/Master.vSum;
kkado 0:c5ca205c0a80 57 }
kkado 0:c5ca205c0a80 58
kkado 0:c5ca205c0a80 59 //Calculate vSum
kkado 1:db0c24aebb8a 60 void STMstation_synth::calc_vSum(){
kkado 0:c5ca205c0a80 61 uint8_t active = 0; //Number of active channels
kkado 0:c5ca205c0a80 62 uint16_t accum = 0; //Sum of volume channel values
kkado 0:c5ca205c0a80 63 bool over = 0; //Is any value over 127?
kkado 0:c5ca205c0a80 64 for(int i=0; i<CHANNELS; i++){
kkado 0:c5ca205c0a80 65 if(Master.notes[i] != NULL){
kkado 0:c5ca205c0a80 66 active++;
kkado 0:c5ca205c0a80 67 accum += Master.vol[i][Master.index[i]];
kkado 0:c5ca205c0a80 68 if(Master.vol[i][Master.index[i]] > 127){over = 1;}
kkado 0:c5ca205c0a80 69 }
kkado 0:c5ca205c0a80 70 }
kkado 0:c5ca205c0a80 71 if(accum == 0) {Master.vSum = 1;}
kkado 0:c5ca205c0a80 72 else if(over == 0) {Master.vSum = active*127;}
kkado 0:c5ca205c0a80 73 else {Master.vSum = accum;}
kkado 0:c5ca205c0a80 74 }
kkado 0:c5ca205c0a80 75
kkado 0:c5ca205c0a80 76 // Calculate the envelope
kkado 0:c5ca205c0a80 77 void STMstation_synth::calc_env(){
kkado 0:c5ca205c0a80 78 for(int i=0; i<CHANNELS; i++){
kkado 0:c5ca205c0a80 79 if(Master.notes[i] != NULL){
kkado 0:c5ca205c0a80 80 if(Master.counter[i] < Master.envAtkEnd[i]){
kkado 0:c5ca205c0a80 81 if(Master.atkSlope == 0){Master.env[i] = 1;}
kkado 0:c5ca205c0a80 82 else{Master.env[i] = Master.atkSlope[i]*Master.counter[i];}
kkado 0:c5ca205c0a80 83 }
kkado 0:c5ca205c0a80 84 else if(Master.counter[i] > Master.envRelStart[i]){
kkado 0:c5ca205c0a80 85 if(Master.relSlope == 0){Master.env[i] = 1;}
kkado 0:c5ca205c0a80 86 else{Master.env[i] = Master.relOffset[i] - Master.relSlope[i]*Master.counter[i];}
kkado 0:c5ca205c0a80 87 }
kkado 0:c5ca205c0a80 88 else{
kkado 0:c5ca205c0a80 89 Master.env[i] = 1;
kkado 0:c5ca205c0a80 90 }
kkado 0:c5ca205c0a80 91 }
kkado 0:c5ca205c0a80 92 }
kkado 0:c5ca205c0a80 93 }
kkado 0:c5ca205c0a80 94
kkado 0:c5ca205c0a80 95 //Square wave switching function
kkado 0:c5ca205c0a80 96 int8_t STMstation_synth::square(float _halfperiod, uint16_t _counter){
kkado 0:c5ca205c0a80 97 if(_halfperiod == 0){
kkado 0:c5ca205c0a80 98 return 0;;
kkado 0:c5ca205c0a80 99 }
kkado 0:c5ca205c0a80 100
kkado 0:c5ca205c0a80 101 uint32_t _div = _counter/_halfperiod;
kkado 0:c5ca205c0a80 102 uint32_t _div2 = _div % 2;
kkado 0:c5ca205c0a80 103 if(_div2 == 0){
kkado 0:c5ca205c0a80 104 return -1;
kkado 0:c5ca205c0a80 105 }
kkado 0:c5ca205c0a80 106 else{
kkado 0:c5ca205c0a80 107 return 1;
kkado 0:c5ca205c0a80 108 }
kkado 0:c5ca205c0a80 109 }
kkado 0:c5ca205c0a80 110
kkado 0:c5ca205c0a80 111 //Triangle wave function
kkado 0:c5ca205c0a80 112 void STMstation_synth::calc_triangle(uint8_t _channel, float _halfperiod, float _trislope, uint32_t _counter){
kkado 0:c5ca205c0a80 113 if(_halfperiod == 0){
kkado 0:c5ca205c0a80 114 Master.triVal[_channel] = 0;
kkado 0:c5ca205c0a80 115 return;
kkado 0:c5ca205c0a80 116 }
kkado 0:c5ca205c0a80 117
kkado 0:c5ca205c0a80 118 int8_t _sign = 2*(uint32_t(_counter/_halfperiod) % 2) - 1;
kkado 0:c5ca205c0a80 119 Master.triVal[_channel] += _sign*_trislope;
kkado 0:c5ca205c0a80 120 if(_counter == 0){
kkado 0:c5ca205c0a80 121 Master.triVal[_channel] = 1;
kkado 0:c5ca205c0a80 122 }
kkado 0:c5ca205c0a80 123 if(Master.triVal[_channel] < -1){
kkado 0:c5ca205c0a80 124 Master.triVal[_channel] = -1;
kkado 0:c5ca205c0a80 125 }
kkado 0:c5ca205c0a80 126 else if(Master.triVal[_channel] > 1){
kkado 0:c5ca205c0a80 127 Master.triVal[_channel] = 1;
kkado 0:c5ca205c0a80 128 }
kkado 0:c5ca205c0a80 129 }
kkado 0:c5ca205c0a80 130
kkado 0:c5ca205c0a80 131 //Calculate noise
kkado 0:c5ca205c0a80 132 void STMstation_synth::calc_noise(uint8_t _channel, uint8_t _freq, uint32_t _counter){
kkado 0:c5ca205c0a80 133 if(_freq == 0){
kkado 0:c5ca205c0a80 134 Master.noiseVal[_channel] = 0;
kkado 0:c5ca205c0a80 135 return;
kkado 0:c5ca205c0a80 136 }
kkado 0:c5ca205c0a80 137
kkado 0:c5ca205c0a80 138 uint32_t _noisePeriod = (16-_freq);
kkado 0:c5ca205c0a80 139 if(_counter % _noisePeriod == 0){
kkado 0:c5ca205c0a80 140 Master.noiseVal[_channel] = 1-(rand() % 257)/128.0f;
kkado 0:c5ca205c0a80 141 }
kkado 0:c5ca205c0a80 142 }
kkado 0:c5ca205c0a80 143
kkado 0:c5ca205c0a80 144 //Calculate values
kkado 1:db0c24aebb8a 145 void STMstation_synth::calc_val(){
kkado 0:c5ca205c0a80 146 Master.val = 128;
kkado 0:c5ca205c0a80 147 for(int i=0; i<CHANNELS; i++){
kkado 0:c5ca205c0a80 148 if(Master.notes[i] != NULL){
kkado 0:c5ca205c0a80 149 if(Master.timbre[i] == 0){
kkado 0:c5ca205c0a80 150 Master.val += 127*arm_sin_f32(Master.sineCoef[i]*Master.counter[i])*Master.env[i]*Master.volCoef[i];
kkado 0:c5ca205c0a80 151 }
kkado 0:c5ca205c0a80 152 else if(Master.timbre[i] == 1){
kkado 0:c5ca205c0a80 153 Master.val += 127*square(Master.halfPeriod[i], Master.counter[i])*Master.env[i]*Master.volCoef[i];
kkado 0:c5ca205c0a80 154 }
kkado 0:c5ca205c0a80 155 else if(Master.timbre[i] == 2){
kkado 0:c5ca205c0a80 156 calc_triangle(i,Master.halfPeriod[i], Master.triSlope[i], Master.counter[i]);
kkado 0:c5ca205c0a80 157 Master.val += 127*Master.triVal[i]*Master.env[i]*Master.volCoef[i];
kkado 0:c5ca205c0a80 158 }
kkado 0:c5ca205c0a80 159 else if(Master.timbre[i] == 3){
kkado 0:c5ca205c0a80 160 calc_noise(i,Master.freqIndex[i],Master.counter[i]);
kkado 0:c5ca205c0a80 161 Master.val += 127*Master.noiseVal[i]*Master.env[i]*Master.volCoef[i];
kkado 0:c5ca205c0a80 162 }
kkado 0:c5ca205c0a80 163 }
kkado 0:c5ca205c0a80 164 }
kkado 0:c5ca205c0a80 165 }
kkado 0:c5ca205c0a80 166
kkado 0:c5ca205c0a80 167 //Remove channel
kkado 0:c5ca205c0a80 168 void STMstation_synth::clear_channel(uint8_t _channel){
kkado 0:c5ca205c0a80 169 Master.notes[_channel] = NULL;
kkado 0:c5ca205c0a80 170 Master.durations[_channel] = NULL;
kkado 0:c5ca205c0a80 171 Master.AR[_channel] = NULL;
kkado 0:c5ca205c0a80 172 Master.vol[_channel] = NULL;
kkado 0:c5ca205c0a80 173 Master.max[_channel] = NULL;
kkado 0:c5ca205c0a80 174 Master.repeat[_channel] = NULL;
kkado 0:c5ca205c0a80 175 Master.counter[_channel] = NULL;
kkado 0:c5ca205c0a80 176 Master.index[_channel] = NULL;
kkado 0:c5ca205c0a80 177 *(Master.endptr[_channel]) = 1;
kkado 0:c5ca205c0a80 178 }
kkado 0:c5ca205c0a80 179
kkado 0:c5ca205c0a80 180 //Stop track
kkado 0:c5ca205c0a80 181 void STMstation_synth::stop_track(melody &newMelody){
kkado 0:c5ca205c0a80 182 for(uint16_t i=0; i<CHANNELS; i++){
kkado 0:c5ca205c0a80 183 if(newMelody.notes[i] != NULL){
kkado 0:c5ca205c0a80 184 clear_channel(i);
kkado 0:c5ca205c0a80 185 }
kkado 0:c5ca205c0a80 186 }
kkado 0:c5ca205c0a80 187 }
kkado 0:c5ca205c0a80 188
kkado 0:c5ca205c0a80 189 //Check if track is playing, returns 0 if all channels ended, 1 otherwise
kkado 0:c5ca205c0a80 190 bool STMstation_synth::check_track(melody &newMelody){
kkado 0:c5ca205c0a80 191 for(uint16_t i=0; i<CHANNELS; i++){
kkado 0:c5ca205c0a80 192 if(newMelody.ended[i] == 0){
kkado 0:c5ca205c0a80 193 return 1;
kkado 0:c5ca205c0a80 194 }
kkado 0:c5ca205c0a80 195 }
kkado 0:c5ca205c0a80 196 return 0;
kkado 0:c5ca205c0a80 197 }
kkado 0:c5ca205c0a80 198
kkado 0:c5ca205c0a80 199 //Check if notes are finished playing
kkado 0:c5ca205c0a80 200 void STMstation_synth::check_end(){
kkado 0:c5ca205c0a80 201 uint32_t _duration;
kkado 0:c5ca205c0a80 202 for(int i=0; i<CHANNELS; i++){
kkado 0:c5ca205c0a80 203 _duration = floor(0.5f + FSAMP*60.0f/Master.bpm[i]*(Master.durations[i][Master.index[i]]+1.0f)/16.0f);
kkado 0:c5ca205c0a80 204 if(Master.counter[i] >= _duration){
kkado 0:c5ca205c0a80 205 if(Master.index[i] < Master.max[i]){
kkado 0:c5ca205c0a80 206 Master.index[i]++;
kkado 0:c5ca205c0a80 207 }
kkado 0:c5ca205c0a80 208 else{
kkado 0:c5ca205c0a80 209 if(Master.repeat[i] == 1){
kkado 0:c5ca205c0a80 210 Master.index[i] = 0;
kkado 0:c5ca205c0a80 211 *(Master.endptr[i]) = 1;
kkado 0:c5ca205c0a80 212 }
kkado 0:c5ca205c0a80 213 else{
kkado 0:c5ca205c0a80 214 clear_channel(i);
kkado 0:c5ca205c0a80 215 *(Master.endptr[i]) = 1;
kkado 0:c5ca205c0a80 216 }
kkado 0:c5ca205c0a80 217 }
kkado 0:c5ca205c0a80 218 Master.counter[i] = 0;
kkado 0:c5ca205c0a80 219 }
kkado 0:c5ca205c0a80 220 else{
kkado 0:c5ca205c0a80 221 Master.counter[i]++;
kkado 0:c5ca205c0a80 222 }
kkado 0:c5ca205c0a80 223 }
kkado 0:c5ca205c0a80 224 }
kkado 0:c5ca205c0a80 225
kkado 0:c5ca205c0a80 226 //Check if a new note is starting and if we need to recalculate coefficients
kkado 0:c5ca205c0a80 227 void STMstation_synth::check_start(){
kkado 0:c5ca205c0a80 228 for(int i=0; i<CHANNELS; i++){
kkado 0:c5ca205c0a80 229 if(Master.counter[i] == 0){
kkado 1:db0c24aebb8a 230 calc_vSum();
kkado 0:c5ca205c0a80 231 calc_coefs(i);
kkado 0:c5ca205c0a80 232 }
kkado 0:c5ca205c0a80 233 }
kkado 0:c5ca205c0a80 234 }
kkado 0:c5ca205c0a80 235
kkado 0:c5ca205c0a80 236 //Play dat funky music
kkado 1:db0c24aebb8a 237 void STMstation_synth::note(){
kkado 0:c5ca205c0a80 238 check_start();
kkado 0:c5ca205c0a80 239 calc_env();
kkado 1:db0c24aebb8a 240 calc_val();
kkado 0:c5ca205c0a80 241 tone.pulsewidth_ticks(Master.val);
kkado 0:c5ca205c0a80 242 check_end();
kkado 0:c5ca205c0a80 243 }
kkado 0:c5ca205c0a80 244
kkado 0:c5ca205c0a80 245 //Start playing a tune
kkado 0:c5ca205c0a80 246 void STMstation_synth::play(melody &newMelody, uint8_t refChannel, uint16_t newIndex){
kkado 0:c5ca205c0a80 247 uint32_t pos = 0;
kkado 0:c5ca205c0a80 248 uint32_t rem;
kkado 0:c5ca205c0a80 249 uint16_t chanIndex;
kkado 0:c5ca205c0a80 250
kkado 0:c5ca205c0a80 251 //pc.printf("Playing track...\n");
kkado 0:c5ca205c0a80 252 //pc.printf("refChannel = %d, newIndex = %d\n", refChannel, newIndex);
kkado 0:c5ca205c0a80 253
kkado 0:c5ca205c0a80 254 //Calculate how far into the track we're at, in terms of samples
kkado 0:c5ca205c0a80 255 for(uint16_t i=0; i<newIndex; i++){
kkado 0:c5ca205c0a80 256 pos += floor(0.5f + FSAMP*60.0f/newMelody.bpm*(newMelody.durations[refChannel][i]+1.0f)/16.0f);
kkado 0:c5ca205c0a80 257 }
kkado 0:c5ca205c0a80 258
kkado 0:c5ca205c0a80 259 //pc.printf("pos = %d\n",pos);
kkado 0:c5ca205c0a80 260
kkado 0:c5ca205c0a80 261 for(int i=0; i<CHANNELS; i++){
kkado 0:c5ca205c0a80 262 if(newMelody.notes[i] != NULL){
kkado 0:c5ca205c0a80 263 chanIndex = 0;
kkado 0:c5ca205c0a80 264 rem = pos;
kkado 0:c5ca205c0a80 265 for(uint16_t j=0; j<=newMelody.max[i]; j++){
kkado 0:c5ca205c0a80 266 if(rem < floor(0.5f + FSAMP*60.0f/newMelody.bpm*(newMelody.durations[i][j]+1.0f)/16.0f)){
kkado 0:c5ca205c0a80 267 *(Master.endptr[i]) = 1; //Important! Overwrite the old .ended back to zero!
kkado 0:c5ca205c0a80 268 Master.notes[i] = newMelody.notes[i];
kkado 0:c5ca205c0a80 269 Master.durations[i] = newMelody.durations[i];
kkado 0:c5ca205c0a80 270 Master.AR[i] = newMelody.AR[i];
kkado 0:c5ca205c0a80 271 Master.vol[i] = newMelody.vol[i];
kkado 0:c5ca205c0a80 272 Master.max[i] = newMelody.max[i];
kkado 0:c5ca205c0a80 273 Master.repeat[i] = newMelody.repeat[i];
kkado 0:c5ca205c0a80 274 Master.endptr[i] = &newMelody.ended[i]; //Now we set the .ended pointer to the new track's
kkado 0:c5ca205c0a80 275 *(Master.endptr[i]) = 0; //Now we set the new track's .ended back to zero.
kkado 0:c5ca205c0a80 276 Master.counter[i] = rem;
kkado 0:c5ca205c0a80 277 Master.index[i] = chanIndex;
kkado 0:c5ca205c0a80 278 Master.bpm[i] = newMelody.bpm;
kkado 0:c5ca205c0a80 279 //pc.printf("Channel %d: chanIndex = %d, rem = %d\n",i,chanIndex,rem);
kkado 0:c5ca205c0a80 280 break;
kkado 0:c5ca205c0a80 281 }
kkado 0:c5ca205c0a80 282 else{
kkado 0:c5ca205c0a80 283 rem -= floor(0.5f + FSAMP*60.0f/newMelody.bpm*(newMelody.durations[i][j]+1.0f)/16.0f);
kkado 0:c5ca205c0a80 284 chanIndex++;
kkado 0:c5ca205c0a80 285 }
kkado 0:c5ca205c0a80 286 }
kkado 0:c5ca205c0a80 287 }
kkado 0:c5ca205c0a80 288 }
kkado 1:db0c24aebb8a 289 calc_vSum();
kkado 0:c5ca205c0a80 290 for(uint16_t i=0; i<CHANNELS; i++){
kkado 0:c5ca205c0a80 291 if(Master.notes[i]!=NULL){
kkado 0:c5ca205c0a80 292 calc_coefs(i);
kkado 0:c5ca205c0a80 293 }
kkado 0:c5ca205c0a80 294 }
kkado 0:c5ca205c0a80 295 }