Dither widget

This is the widget I used for the animated dither chart in my Audio Dither Explained video. “Run” animates the dither process, otherwise it changes only when you change the amplitude setting. “Connect” connects the dots; this is simply a visual aid, making it easily to follow the sequence of samples.

Posted in Digital Audio, Dither, Widgets | Leave a comment

Audio Dither Explained video

This video discusses the how and why of dither.

Posted in Digital Audio, Dither, Video | Leave a comment

More about source code

I was admonished (in not a nice way—not terribly rude, but quite to the point that I don’t know what I’m doing) by an anonymous visitor, who concluded that I don’t know much about C++ and maybe should have written it in a different language (why, when the purpose is to give people C++ code?). I would reply to him if he had left a real email address, but maybe I should make a few things clear to all.

I supply bare bones code. I try to leave out anything not necessary, and avoid even fairly obvious optimizations (and some less obvious that would make the code more difficult to understand. For instance, the wavetables could be one sample larger and let you avoid testing for wrap-around and the resulting branch). See About source code examples.

There is a common misconception that if your class is suitable for subclassing, then the destructor must be declared virtual. This is not true. If it were, the C++ architects would have simply built it in.

So, to some (including the anonymous commenter), it may seem confusing that I don’t declare destructors as virtual, which they might conclude to imply no subclassing, while declaring variables as protected (not private), implying support for subclassing. There is a method to this apparent madness: First, I don’t expect that the object will be subclassed by you, and I want to keep it efficient. So, I don’t declare destructors virtual, which would create some overhead we don’t need. However, if you do choose to subclass, then it’s important that you have access to certain variables, hence the protected declarations where appropriate.

However, that fact is that base class destructors need not be virtual when subclassed—that’s a requirement only if you want to delete them via the base class. By not declaring them virtual, we get the lack of vtable in the typically case (of no subclasses), while still allowing subclassing.

Also, I don’t mess with namespaces, and avoid templates even though they would be useful—again, trying to focus on the code that implements the DSP algorithm. I try to fit as much of the pertinent code on the screen for you as possible, so while I like comments in my code, I keep them brief.

And I don’t go through and declare every non-changing variable as const. This is not open source code. I’m not trying to protect you or some group you are working on code with from yourselves. I’m giving you seeds, not a framework, and it’s not in a Git repository. If I keep it simple enough, I figure you’ll be able to pull the bits out that matter for you into your own code. I don’t have time for both open source and tutorials—you can find a ton of open source oscillators, filters, and modulators on the internet, and I think you’ll find that the authors do not teach you how and why the code works. The latter is few and far between, and that’s why I’m here.

I’m open to suggestions, but realize that I do think about my choices, even if it’s not apparent. So, please, let’s make it a discussion. And the anonymous fellow is invited to point us to his contributions to the public. ;-)

Posted in Source Code | 3 Comments

Pole-Zero placement v2

Pole mag
Pole angle
Zero mag
Zero angle
Sample rate (Hz)
Plot

A new pole-zero calculator

An JavaScript remake of the old Java-based pole-zero placement applet—visit that page for tips on pole-zero locations for standard biquads. The main additions are input fields for precision pole-zero placement, and an option to display the response with a log frequency scale.

The basic idea is that poles blow, zeros suck. Think of poles as controlling a frequency-dependent feedback or resonance—the impulse response of a pole inside the unit circle decays, while one outside is like runaway feedback (think of a mic feeding back into a loudspeaker). A pole on the unit circle gives a sustained oscillation (but watch out for numerical errors—keep your poles inside the unit circle, typically). Zeros absorb a particular frequency; when on the unit circle, they absorb the corresponding frequency completely.

So, poles push the frequency response up around their corresponding frequency, and zeros pull down around theirs. Keep in mind that the frequency response graph is normalized, just as the filter coefficients are. So, while a pole pushes up the response, it appears as though all other frequencies are being pushed down instead. Of course, normalization is important in practical application, but be aware of it when visualizing how poles and zeros interact.

Posted in Biquads, Digital Audio, Filters, IIR Filters, Widgets | 4 Comments

Biquad calculator v2

Type:

Plot:
Sample rate (Hz)
Fc (Hz)
Q
Gain (dB)

Here’s an update of the biquad calculator. It adds one-pole highpass and lowpass filters, and frequency, Q, and gain sliders. The sliders cover the range of typical audio settings, and are valuable for getting a quick feel for how the filters respond. But for precise settings and a wider range of parameters, type settings into the edit fields. (For this reason, the connection between sliders and edit fields are one-way—sliders update the edit fields, but changing the fields does not change the sliders positions accordingly.)

Review the biquad direct forms here; direct form I is best for fixed point processors, while direct for II transposed is best for floating point implementations.

Posted in Biquads, Digital Audio, Filters, IIR Filters, Widgets | 17 Comments

Envelope generators—ADSR widget

Attack
Decay
Sustain
Release
Attack Curve
Decay/Release Curve
Plot:

This is the widget I used in making the ADSR video. It’s a JavaScript recreation of my C++ source code.

Posted in Envelope Generators, Synthesizers, Widgets | 7 Comments

Envelope generators—ADSR video

Let me know how you like this one!

Posted in Envelope Generators, Synthesizers, Video | 3 Comments

Envelope generators—ADSR code

First, a brief example of how to use the ADSR code:

// create ADSR env
ADSR *env = new ADSR();

// initialize settings
env->setAttackRate(.1 * sampleRate);  // .1 second
env->setDecayRate(.3 * sampleRate);
env->setReleaseRate(5 * sampleRate);
env->setSustainLevel(.8);
…
// at some point, by MIDI perhaps, the envelope is gated "on"
env->gate(true);
…
// and some time later, it's gated "off"
env->gate(false);

In your “real-time” thread, where you fill audio buffers for output, you’d use the process command to generate and return the next ADSR output:

// env->process() to generate and return the ADSR output...
outBuf[idx] = filter->process(osc->getOutput()) * env->process();

Functions

Create and destroy:

ADSR(void);
~ADSR(void);

Call the process function at the control rate (which might be once per sample period, or a lower rate if you’ve implemented a lower control sampling rate); it returns the current envelope output. The getOutput function returns the current output only, for convenience:

float process(void);
float getOutput(void);

Set the gate state on (true) or off (false):

void gate(int on);

Set the Attack, Decay, and Release rates (in samples—you can multiply time in seconds by the sample rate), and the sustain level (0.0-1.0); calls these when settings change, not at the sample rate:

void setAttackRate(float rate);
void setDecayRate(float rate);
void setReleaseRate(float rate);
void setSustainLevel(float level);

Adjust the curves of the Attack, or Decay and Release segments, from the initial default values (small number such as 0.0001 to 0.01 for mostly-exponential, large numbers like 100 for virtually linear):

void setTargetRatioA(float targetRatio);
void setTargetRatioDR(float targetRatio);

Reset the envelope generator:

void reset(void);

It may be useful to know what state an envelope generator is in, for sophisticated control applications (typically, though, you won’t need this); a state enum is defined for convenience (for example, ADSR::env_sustain is equivalent to the value 3, and indicates that the current state is Sustain):

int getState(void);
    env_idle = 0, env_attack, env_decay, env_sustain, env_release

Code

Calculations that are done in the “real time” thread are written as inline code in the header file. Calculations that are done asynchronously, at “setup time” (which include patch recall and “knob twiddling”), are in the cpp file functions; this includes sub-calculations of the real-time math that are dependent only on setup-time parameters.

Download ADSR source code

Posted in Envelope Generators, Source Code, Synthesizers | 7 Comments

Envelope generators—ADSR Part 2

Certain aspects of the ADSR are up for interpretation. Exactly how the ADSR re-triggers when not starting from idle, for instance. Also, we can decide whether we want constant rate, or constant time control of the segments. The attack segment is fixed in height, so starting from idle (0.0 level) to peak (we’ll use 1.0 in our implementation—feel free to multiply the output to any level you want), there is no difference between constant rate and constant time. But for the decay and release segments, the distance traveled is dependent on the sustain level. I choose constant rate—this means that the release setting is for a time from the maximum envelope value (1.0) to 0.0; if sustain is set mid-way at 0.5, the release will take less time to complete than if it were at 1.0, but the rate of fall will be the same.

ADSR-2

Time

There is one problem about how much time it takes for an exponential move: In theory, it takes forever.

That’s OK for decay and release, since it will become so tiny after a point that there is no difference between the near-zero output and zero. For attack, however, it’s a big issue. We don’t move to the decay state until the attack state hits the maximum at 1.0. If it never hits, we’re in trouble. No problem—we just aim a little higher than 1.0, and when it crosses 1.0 we move to the decay state.

In practice, floating point numbers have limitations and it won’t take forever (depending on the exact math used). But we’d spend way too much time making unnoticeably tiny moves as we get close to 1.0; certainly, we want to clip the exponential attack. Hardware envelope generators do the same thing, hitting a trigger level to switch to the decay state.

But there’s an aesthetic reason to clip the attack exponential as well. There are a number of reasons that the shape of “decaying” upwards towards a maximum value is the wrong shape for a volume envelope. It’s likely that the popular attack curve for hardware generators was a compromise between an acceptable shape and convenience (it’s easy to generate).

Math

While the math of the curve itself is simple due to the nature of generating a exponential curve iteratively (we just feed back a portion), the math of relating rates to coefficients is a bit more work. I decided that instead of arbitrary (“1-10”) control settings for the rates, I wanted to set the segments by time (in samples) for the segment to complete. So, a setting of 100 would make the envelope attack from 0.0 to 1.0 in 100 samples. A setting of 100 for release would hit 0.0 in 100 samples if sustain were set at maximum, 1.0, but sooner if it were set lower—constant rate, not time.

Again, we need only clip the attack segment, since we wouldn’t notice the difference between a full exponential and a slightly clipped one for the decay and release segments. However, putting a cap on those segments has some advantages. One is that we use a little less processing for envelopes in the idle state. But there’s another trick I chose to implement, after experimenting a bit and giving thought to exponential versus linear segments. Read on.

Shape control

One way to handle terminating exponential segments would be to set a threshold that is deemed “close enough”. For instance, as the attack segment gets within, say, 0.001 of 1.0, we simply set the output to 1.0 and move to the decay state. But if we take a slightly different approach, shooting for a target of 1.001 instead, and terminating when it hits or crosses 1.0, then we won’t have a noticeable step if we decide to make that “overshoot” value much larger.

Why would we want to make it much larger? That would wreck our nice exponential. In fact, if we make it too large, the moves would be virtually…linear! So, we could use a small number to stay near-exponential, or move to larger numbers to move towards linear segments, giving us a very versatile ADSR with curve control very simply.

More math

Usually, the rate of an exponential is based on the time it takes to decay to half the current value. But since we’re truncating our exponentials, I chose to define our rate controls as the time it takes to complete our attack from 0.0 to 1.0, or the decay or release to move from 1.0 to 0.0 (which happens for decay when the sustain level is set to 0.0, or release when sustain is 1.0).

Exponential calculations can be relatively CPU intensive. But, fortunately, envelope generators produce their output iteratively. And an iterative implementation of the exponential calculation is a trivial computation. We can calculate the first step of the exponential, whenever the appropriate rate setting is changed, then use it as the multiplier for subsequent iterations (remember the one-pole filter? Simple multiplies and additions).

rate = exp(-log((1 + targetRatio) / targetRatio) / time);

where targetRatio is our “overshoot” value, the constant 1 is because we are moving a distance of 1 (0.0 to 1.0, or 1.0 to 0.0), and time is the number of samples for the move. Typically, we’ll pick a targetRatio that we like (a larger number to approximate linear, or a small fraction to approach exponential); time comes from our rate knob, perhaps via a lookup table so that we can span from 1 sample to five or ten seconds worth of samples in a reasonable manner.

I chose to refer to the overshoot adjustment as a “target ratio” because it’s related to the size of the move, and it’s helpful to think of it in terms of dB, especially related to loudness. For the release to decay to -60 dB, for instance, we use a value of 0.001; -80 dB is 0.0001. Using smaller values will have a significant effect on how “wide” the top of the attack segment is, at a given attack rate setting. To convert from dB to the ratio value, use 10 raised to the power of your dB value divided by 20; in C, “pow(10, val / 20.0)”.

Another pleasant byproduct of using this “target ratio” (overshoot) for all curves is that we don’t need to guard against output decaying into denormals and ruining performance.

Next: Code

Up next is source code. As usual, I try to keep the code simple yet flexible. You may want to add features to auto-retrigger the envelope for repeating patterns, add delay and hold features, or add additional segments—that’s where you distinguish yourself. And this ADSR has features lacking in many implementations already—adjustable curves, and precise specification of attack time in samples, for instance.

Posted in Envelope Generators, Synthesizers | 4 Comments

Envelope generators—ADSR Part 1

After discussing the exponential decay of the one-pole filter in a recent article, I couldn’t help but think about envelope generators. Besides, it would be handy to have one to test out some of these other components I’ve been writing about.

The staple of analog synthesizers is the ADSR envelope generator. Sure, when creating an envelope computationally, we don’t have the limitations of hardware, so we could make envelopes of unlimited complexity. But the abundance of “virtual analog” plug-ins attest to the fact that the simple ADSR is a very effective design—and mandatory if we’re simulating most vintage gear. And it’s more manageable to explain how to make an ADSR, and let you take it from there if you wish.

States and gates

Envelope generators are defined by their states. Typically, an envelope generator of a synthesizer at rest is in an idle state, outputting zero level. When you press a key, it raises a gate to the “on” state, triggering the envelope generator into its initial attack state. The output of the envelope generator begins to rise at an attack rate determined by a user control. As long as the key remains down, keeping the gate “on”, the envelope rises until it reaches its maximum output. At that time, it switches to the decay state, and moves towards the user-defined sustain level (set anywhere from zero to maximum output) at the rate set by another user control. Upon reaching the sustain level, the envelope generator switches to the sustain state, and remains there as long as the key is held. Once the key is released and the gate changes to “off”, the envelope generator switches to the release state, and begins moving towards zero at the rate set by a fourth user control.

State Action
Idle do nothing (output remains at zero)
Attack continue increasing output at attack rate; if max is reached, change state to Decay
Decay continue reducing output at decay rate; if sustain level reached, change state to Sustain
Sustain do nothing (output remains at the sustain level)
Release continue reducing output at release rate; if zero is reached, change state to Idle

Note that the Idle and Sustain states have no exit rules. Moves out of these states are driven by a gate signal—driven by a synthesizer key press and release, typically, though the gate can be from a low frequency oscillator or step sequencer, for instance. Here are the additional state rules, driven by the gate transitions:

Gate transition Action
from “off” to “on” set state to Attack
from “on” to “off” if state is not Idle, then set state to Release

Curves

Some envelope generators—more often of the software variety than hardware—have attack, decay, and release segments that rise or fall in a straight line. But hardware envelope generators use exponential segments, typically. The exponential moves come from the charging and discharging of capacitors with current limited by resistors—one-pole filters, basically. Besides being easy to design with simple transistors, resistors, and capacitors, the exponential segments work especially well for amplitude envelopes, since we hear on a log scale (the exponential control against the log response makes the volume changes sound linear with respect to loudness).

Let’s take a look at how this works in our one-pole filter. You can view a one-pole filter as simply feeding back a percentage of the previous output, with the percentage value controlling the rate of the resulting exponential curve.

A simple AR envelope generator

To get the leanest view of how an envelope generator works, let’s start with the simplest. Synthesizers generate gate signals, typically, when a key is pressed—the gate starts at 0v with no keys down, jumps to 1v when a key is pressed, and back to 0v when the key is released. If we use that as the control signal to our VCA, we’d have an organ-like instant on and off—not very flexible.

But if we route that into a one-pole filter—a resistor feeding a capacitor to ground—the processed gate would ramp up and down with an exponentially-shaped curve, at a rate depending on the resistor and capacity values multiplied together. (Since it’s more difficult and costly to make variable capacitors, we use a fixed capacitor and a variable resistor.) Here’s a circuit simulation of this basic AR (Attack-Release) envelope generator:

one-pole ckt
one-pole ckt step

This generator is just as simple in software—a gate that toggles from 0 to 1, fed into a one-pole filter, which is a delay and a multiplier taking the place of a capacitor and resistor. Here’s an iterative simulation done in a spreadsheet, using a feedback value calculated to be equivalent to the capacitor and resistor values shown in the circuit:

AR calc

Next: ADSR

But the AR isn’t a very flexible envelope generator—it’s a simple shape, and a single control value sets both attack and decay times. It would be useful for flute-type sounds, but not for guitar, for instance. For guitar, we’d like a fast attack, a slow decay, and a quick release. For that kind of complexity, we need a more elaborate circuit, with transistors to switch in difference resistances and target levels for capacitor charging.

Next up, we’ll delve into the specifics of an ADSR envelope generator in software.

Posted in Envelope Generators, IIR Filters, Synthesizers | Leave a comment