First post, by superfury
The current soundfont synthesizer I've implemented uses a relatively simple ADSR envelope and volume lookup.
The ADSR converts the sustain setting (in attenuation 1/10th decibels) to an inverted range (to facilitate the ADSR internally ramping from 0 to 1(attack), then 1 to sustain(decay) and sustain to 0(release). The release phase can preempt the pre-sustain phases, which causes the change that the current level (instead of normal sustain level) is the starting point to ramp to 0.
Since the sustain level (both for volume and modulation envelopes) is actually attenuation, the sustain level is simply inverted in the range to calculate the sustain level to use if sustain is fully reached.
hassustain = 1; //Default: valid sustain!fullsustain = 0; //Default: no full sustain!if (envelopetype) //Volume envelope? attenuation! Unit is cB.{sustainrange = 1440; //Range of 1440cB!}else //0.1 Percentage?{sustainrange = 1000; //Range of 1000 x0.1%}sustain = LIMITRANGE(sustain,0,sustainrange); //Limit of [0 - range] cB/%.!//Volume envelope? It's attenuation, not amplification (reverse it to obtain attenuation!). This applied to modulation envelopes too, apparently, although violating documentation.sustain = sustainrange-sustain; //Reverse the range, as it's attenuation instead!sustainfactor = ((float)sustain); //We're on a rate of 1440cB attenuation, normalized!sustainfactor /= (float)sustainrange; //Normalized!if (sustain==0) //No sustain? Fix rounding too!{sustainfactor = 0.0f; //No sustain!hassustain = 0; //No sustain!}else if (sustain == sustainrange) //Fix rounding for max level (full range){sustainfactor = 1.0f; //Full sustain!fullsustain = 1; //Full sustain!}
hassustain being cleared disables any release. fullsustain being set disables any decay phase (no decay at full sustain).
hassustain also affects the note to be releasing irrespectively when sustain of the note is still pending (on the MIDI side of things). Thus facilitating percussion instruments.
This part seems to be behaving properly?
The curves are linear, except the attack curve:
//val needs to be a normalized input! Performs a convex from 0 to 1!float attackconvex(float val, float maxvalue){float result;if (val <= 0.0f) //Invalid?{return 0.0f; //Nothing!}result = 1.0f + ((40.0 / 96.0) * log10f(val / maxvalue)); //Convert to the 0.0-0.9 range!result = LIMITRANGE(result, 0.0f, 1.0f); //Limit the range!return result; //Give the result!}
Then, the initial Attenuation setting is combined with the volume envelope output (range 0.0-1.0). The volume envelope is multiplied by it's real range (1440 tenths of a centibel) and added to the initial attenuation (which has the same unit).
One final thing being added is the master volume knob setting of the MIDI synthesizer itself (this adds from 0 to 1440 units of attenuation based on said setting (0 being no attenuation, 1440 for full attenuation).
The resulting attenuation is simply capped to be from 0 to 1440 tenths of a decibel.
That capped value is looked up in a lookup table, which gives the floating point number that is multiplied to the sample to attenuate (a normal 16-bit ranged sample):
OPTINLINE float MIDIattenuate(float value){return (float)powf(10.0f, value / -200.0f); //Generate default attenuation!}
From what I can tell of found documentation, this seems fine.
But various instruments sound way to soft or sometimes way too loud to what it should be?
Anyone knows what might be going wrong?
Author of the UniPCemu emulator.
UniPCemu Git repository
UniPCemu for Android, Windows, PSP, Vita and Switch on itch.io