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:16:42 2017 +0000
Revision:
0:c5ca205c0a80
Child:
1:db0c24aebb8a
Last commit before adding documentation

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