Simple synthesizer for STM32F401RE/STMstation.
Dependencies: FastPWM mbed-dsp
This is a basic synthesizer to play music on the STM32F401RE Nucleo/STMstation development board:
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.
Diff: STMstation_synth.cpp
- 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