Beginner FM synthesis - envelope question

Status
Not open for further replies.

eringee

Active member
Hi everyone - getting into the Teensy Audio Library is super fun, but I find the synthesis aspects a little opaque at times

Can someone tell me why this code doesn't fade away nicely, but instead clips at the end of the bell-tone? I've been trying to get the bell to fade away and can't understand what's wrong...

I'm working with the DAC not the audioshield, so you'll have to change a bit of code if you want to use the shield...

Thanks for giving me such nice tools to learn with!

Erin


#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

int ringabell = 0;
int testLed = 13;
boolean noteGo;
boolean noteStop;

// GUItool: begin automatically generated code
AudioSynthWaveform waveform1; //xy=283,52
AudioSynthWaveform waveform2; //xy=299,262
AudioEffectEnvelope envelope1; //xy=429,51
AudioSynthWaveformSineModulated sine_fm1; //xy=435,126
AudioSynthWaveformSineModulated sine_fm2; //xy=451,266
AudioEffectEnvelope envelope3; //xy=511,360
AudioEffectEnvelope envelope2; //xy=529,192
AudioMixer4 mixer1; //xy=690,230
AudioOutputAnalog dac1; //xy=861,250
AudioConnection patchCord1(waveform1, envelope1);
AudioConnection patchCord2(waveform2, sine_fm2);
AudioConnection patchCord3(envelope1, sine_fm1);
AudioConnection patchCord4(sine_fm1, envelope2);
AudioConnection patchCord5(sine_fm2, envelope3);
AudioConnection patchCord6(envelope3, 0, mixer1, 1);
AudioConnection patchCord7(envelope2, 0, mixer1, 0);
AudioConnection patchCord8(mixer1, dac1);
// GUItool: end automatically generated code

void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
AudioMemory(8);

waveform1.begin(0.25, 400, WAVEFORM_SINE);
waveform2.begin(0.25, 400, WAVEFORM_SINE);

sine_fm1.frequency(200);
sine_fm1.amplitude(0.5);

sine_fm2.frequency(200);
sine_fm2.amplitude(0.5);

mixer1.gain(0, 0);
//mixer1.gain(1, 0);

envelope1.attack(2);
envelope1.hold(1);
envelope1.decay(3000);
envelope1.sustain(0);
envelope1.release(0.0);

envelope2.attack(2);
envelope2.hold(1);
envelope2.decay(3000);
envelope2.sustain(0);
envelope2.release(0.0);

envelope3.attack(2);
envelope3.hold(1);
envelope3.decay(200);
envelope3.sustain(0.1);
envelope3.release(500);

pinMode(testLed, OUTPUT);
}

elapsedMillis every1; //note that this occurs outside the setup

void loop() {
// put your main code here, to run repeatedly:

if(every1 >= 1) {
ringabell = ringabell+1;

if(ringabell == 2) {
sine_fm2.amplitude(0.5);
envelope3.noteOn();
digitalWrite(testLed, HIGH);
}

if(ringabell == 1200) {
sine_fm2.amplitude(0.0);
noteStop=1;
digitalWrite(testLed, LOW);
}
if(ringabell >= 3000) ringabell = 0;

every1 = every1-1;
}

if(noteStop==1) {
envelope3.noteOff();
noteStop=0;
}


}
 
maybe you can search internet for how to use

Maybe I should clarify: I have watched the awesome tutorial video probably 3 times, I have checked out the documentation on the GUI, I still find that getting a sustained sound after the attack is not yielding great results.

I was trying to run the code in a loop that reduced the massive numbers but I will have to find a different workaround, once I just used huge numbers in the main loop the pops are eliminated, but I still can't get the bell to "ring"

I have looked at many FM synthesis videos online but they aren't specific to the Teensy environment.

The example code in the oscillator section could be expanded as the "PlaySynthMusic" uses a method way more complicated than I'd need, but I was wondering if someone that worked with this more could help me out in the meantime.

If I can't get an answer here I'll keep looking on the internet, obviously, thanks!



#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

int ringabell = 0;
int testLed = 13;

// GUItool: begin automatically generated code
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioSynthWaveform waveform2; //xy=272,192
AudioSynthWaveform waveform3; //xy=281,321
AudioSynthWaveform waveform1; //xy=283,52
AudioSynthWaveform waveform4; //xy=326,439
AudioSynthWaveformSineModulated sine_fm2; //xy=408,237
AudioSynthWaveformSineModulated sine_fm3; //xy=418,323
AudioSynthWaveformSineModulated sine_fm1; //xy=435,126
AudioSynthWaveformSineModulated sine_fm4; //xy=503,439
AudioEffectEnvelope envelope2; //xy=529,192
AudioEffectEnvelope envelope3; //xy=553,326
AudioEffectEnvelope envelope1; //xy=581,116
AudioEffectEnvelope envelope4; //xy=669,422
AudioMixer4 mixer1; //xy=690,230
AudioOutputAnalog dac1; //xy=861,250
AudioConnection patchCord1(waveform2, sine_fm2);
AudioConnection patchCord2(waveform3, sine_fm3);
AudioConnection patchCord3(waveform1, sine_fm1);
AudioConnection patchCord4(waveform4, sine_fm4);
AudioConnection patchCord5(sine_fm2, envelope2);
AudioConnection patchCord6(sine_fm3, envelope3);
AudioConnection patchCord7(sine_fm1, envelope1);
AudioConnection patchCord8(sine_fm4, envelope4);
AudioConnection patchCord9(envelope2, 0, mixer1, 1);
AudioConnection patchCord10(envelope3, 0, mixer1, 2);
AudioConnection patchCord11(envelope1, 0, mixer1, 0);
AudioConnection patchCord12(envelope4, 0, mixer1, 3);
AudioConnection patchCord13(mixer1, dac1);
// GUItool: end automatically generated code

void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
AudioMemory(14);

int x = 523; //C5
int y = x*2+x/10;

int x1 = 783; //G5
int y1 = x1*2+x1/10;

int x2 = 622; //Eb5
int y2 = x2*2+x1/10;

int x3 = 932; //Eb5
int y3 = x2*2+x1/10;

waveform1.begin(0.5, x, WAVEFORM_SAWTOOTH);
waveform2.begin(0.5, x1, WAVEFORM_SQUARE);
waveform3.begin(0.5, x2, WAVEFORM_PULSE);
waveform4.begin(0.5, x3, WAVEFORM_TRIANGLE);

sine_fm1.frequency(y);
sine_fm1.amplitude(0.5);

sine_fm2.frequency(y1);
sine_fm2.amplitude(0.3);

sine_fm3.frequency(y2);
sine_fm3.amplitude(0.3);

sine_fm4.frequency(y3);
sine_fm4.amplitude(0.3);


//mixer1.gain(0, 0);
//mixer1.gain(1, 0);

envelope1.attack(2);
envelope1.hold(0.4);
envelope1.decay(200);
envelope1.sustain(0.005);
envelope1.release(4000);

envelope2.attack(2);
envelope2.hold(0.4);
envelope2.decay(200);
envelope2.sustain(0.005);
envelope2.release(4000);

envelope3.attack(3);
envelope3.hold(0.3);
envelope3.decay(800);
envelope3.sustain(0.005);
envelope3.release(800);

envelope4.attack(3);
envelope4.hold(0.3);
envelope4.decay(800);
envelope4.sustain(0.005);
envelope4.release(800);

pinMode(testLed, OUTPUT);
}

void loop() {

ringabell = ringabell+1;

if(ringabell == 2) {
envelope1.noteOn();
digitalWrite(testLed, HIGH);
}
if(ringabell == 2000) envelope2.noteOn();

if(ringabell == 300000) {
envelope1.noteOff();
digitalWrite(testLed, LOW);
}
if(ringabell == 330000) envelope2.noteOff();

if(ringabell == 350000) envelope3.noteOn();
if(ringabell == 355000) envelope4.noteOn();
if(ringabell == 640000) envelope3.noteOff();
if(ringabell == 600000) envelope4.noteOff();
if(ringabell >= 900000) ringabell = 0;


}
 
Thanks Paul, I'll keep experimenting to try and get a better ring another way. You're right I didn't see the bottom notes section.
 
You can create long envelopes using the DC synth (which does rate-controlled linear ramping between DC levels) and the multiplier effect. That's not nearly as CPU efficient as the highly optimized envelope object, so keep an eye on the max CPU usage.

This also requires your program to call functions for each transition point instead of only noteOn and noteOff, where noteOn conveniently causes the attack, decay and sustain phases to occur automatically. But the DC object does support smooth linear ramping over much longer time scales.

Someday the envelop object will get improved time range, but it's quite difficult to do without compromising the low CPU usage.
 
Envelopes and filters might be better done on the analog side of the design than within the code - you can check out some really simple envelope designs over at http://www.cgs.synth.net/ - click on "modules" then search "envelope" - you can either order boards from them or DIY it! :)
 
I haven't tried it, but if its only a long envelope you want to fade out the sound over several seconds you could set up an array of amplitude values for the waveform or gain for a mixer and use a for/next loop to read the amplitude values as the waveform is playing with a few milliseconds between each amplitude change. If the steps were small it would sound continuous. You could use a spreadsheet to generate a list of values for the array and plot a graph of the values in the spreadsheet and see the shape of your long envelope....Just thinking....
 
The envelope object has limitations to about 200 ms. See the details in the documentation on the design tool.

Eventually the code will be improved to allow longer times, but it's really tough to do so and still keep the code really efficient.

If you want a long envelope, you can make one with the DC object, which has slow ramping between levels and the multiplier object as a "voltage controlled amplifier". But this won't be nearly as efficient. Use the CPU reporting functions to keep an eye on how much processor time you're burning up.
 
If anyone's still watching this old thread, I've *finally* fixed this long-standing limitation in the envelope effect. The fix is on github now, and will be released in Teensyduino 1.37 in June-July 2017 time frame.

https://github.com/PaulStoffregen/Audio/commit/8a5d715dda3903d82d4ebc1aececa40131281014

I increased the internal numerical resolution for gain adjustment from 16 to 30 bits. The prior limit of 0.2 seconds for attack, hold, decay & release is now increased to 11.88 seconds.

I also tried to fix the pop that could occur if you call noteOff() before the sustain phase. Could really use some feedback on the effectiveness of this fix, if anyone is willing to try the latest from github...
 
Status
Not open for further replies.
Back
Top