8 releases
0.2.12alpha.0  Apr 7, 2023 

0.2.11alpha.0  Dec 19, 2022 
0.2.5alpha.0  Jun 21, 2022 
0.2.4alpha.0  Mar 14, 2022 
0.1.42alpha.0  Oct 27, 2021 
#165 in Algorithms
75 downloads per month
Used in surgesynthesizer
155KB
2.5K
SLoC
DigiShaper
This code implements a waveshaper algorithm called DigiShaper. A waveshaper takes an input signal and distorts it in a nonlinear way, producing a new output signal with a different timbre or character. In this case, the DigiShaper algorithm maps the input signal onto a piecewiselinear curve with 16 segments, each of which has a different slope. The amount of distortion applied is controlled by a "drive" parameter.
The DigiShaper algorithm uses a number of mathematical concepts to achieve this result. First, it leverages the concept of piecewiselinear functions, which are functions that are defined by different linear equations on different intervals. In this case, the DigiShaper uses 16 linear equations to define its piecewiselinear curve.
Second, the algorithm uses concepts from digital signal processing, such as sample rate conversion and quantization. The input signal is first scaled by a factor of 16, then quantized to an integer value, and finally scaled back down by a factor of 16. This allows the algorithm to implement the piecewiselinear curve with only a small number of discrete levels, which reduces the computational cost of the algorithm.
Third, the algorithm uses SIMD instructions (SSE2) to perform the calculations in parallel on multiple data points at once. This improves the efficiency of the algorithm and makes it suitable for realtime audio processing applications.
Overall, the DigiShaper algorithm is a relatively simple but effective way to apply distortion to an audio signal, and it leverages a number of mathematical concepts from digital signal processing to achieve its result.
2> basically, it boils down to this equation doesn't it: (double)((int)((double)(xp0inv16.f+1.0)))p00.0625f;
Yes, that's correct. The main logic of the DigiShaper algorithm is implemented in this equation, which is used to calculate the output value for a single sample of the input signal:
(double)((int)((double)(x*p0inv*16.f+1.0)))*p0*0.0625f;
Here's a breakdown of what this equation does:

x
is the input sample value. 
p0
is the drive parameter, which controls the amount of distortion applied to the signal. 
p0inv
is the reciprocal ofp0
, which is precomputed to improve efficiency. 
x * p0inv * 16.f
scales the input sample by a factor of 16 and then multiplies it byp0inv
. 
+ 1.0
shifts the scaled input signal up by 1.0, so that the output values are in the range [1, 17] rather than [0, 16]. 
(double)((int)(...))
rounds the shifted value down to the nearest integer using a cast to an integer data type, which effectively quantizes the input signal. 
* p0 * 0.0625f
scales the quantized value back down by a factor of 16 and then multiplies it byp0
, which applies the distortion to the signal.
So in summary, this equation maps the input signal
onto a piecewiselinear curve with 16 segments,
each of which has a different slope, and the
amount of distortion applied is controlled by the
p0
parameter.
3> could you show me a block diagram?
Certainly! Here's a block diagram that shows the main processing steps of the DigiShaper algorithm:
++ ++
Input signal >  Scale by 16  >  Quantization > ++
++ ++  Piecewiselinear
 Curve Mapping 
++ ++  
Drive parameter >  Reciprocal  >  Scale by 16  >  
++ ++  
 
++ ++  
Quantized value >  Cast to int  >  Scale by p0  >  Output Signal 
++ ++ ++
As you can see, the input signal is first scaled
by a factor of 16 and then quantized to an integer
value, which effectively maps it onto
a piecewiselinear curve with 16 segments. The
quantized value is then scaled back down by
a factor of 16 and multiplied by the drive
parameter p0
, which applies the distortion to
the signal. The output signal is the final result
of this processing. The reciprocal of the p0
parameter is precomputed to improve efficiency,
and SIMD instructions are used to perform the
calculations in parallel on multiple data points
at once.
This code implements a waveshaper algorithm called DigiShaper. A waveshaper takes an input signal and distorts it in a nonlinear way, producing a new output signal with a different timbre or character. In this case, the DigiShaper algorithm maps the input signal onto a piecewiselinear curve with 16 segments, each of which has a different slope. The amount of distortion applied is controlled by a "drive" parameter.
The DigiShaper algorithm uses a number of mathematical concepts to achieve this result. First, it leverages the concept of piecewiselinear functions, which are functions that are defined by different linear equations on different intervals. In this case, the DigiShaper uses 16 linear equations to define its piecewiselinear curve.
Second, the algorithm uses concepts from digital signal processing, such as sample rate conversion and quantization. The input signal is first scaled by a factor of 16, then quantized to an integer value, and finally scaled back down by a factor of 16. This allows the algorithm to implement the piecewiselinear curve with only a small number of discrete levels, which reduces the computational cost of the algorithm.
Third, the algorithm uses SIMD instructions (SSE2) to perform the calculations in parallel on multiple data points at once. This improves the efficiency of the algorithm and makes it suitable for realtime audio processing applications.
Overall, the DigiShaper algorithm is a relatively simple but effective way to apply distortion to an audio signal, and it leverages a number of mathematical concepts from digital signal processing to achieve its result.
2> basically, it boils down to this equation doesn't it: (double)((int)((double)(xp0inv16.f+1.0)))p00.0625f;
Yes, that's correct. The main logic of the DigiShaper algorithm is implemented in this equation, which is used to calculate the output value for a single sample of the input signal:
(double)((int)((double)(x*p0inv*16.f+1.0)))*p0*0.0625f;
Here's a breakdown of what this equation does:

x
is the input sample value. 
p0
is the drive parameter, which controls the amount of distortion applied to the signal. 
p0inv
is the reciprocal ofp0
, which is precomputed to improve efficiency. 
x * p0inv * 16.f
scales the input sample by a factor of 16 and then multiplies it byp0inv
. 
+ 1.0
shifts the scaled input signal up by 1.0, so that the output values are in the range [1, 17] rather than [0, 16]. 
(double)((int)(...))
rounds the shifted value down to the nearest integer using a cast to an integer data type, which effectively quantizes the input signal. 
* p0 * 0.0625f
scales the quantized value back down by a factor of 16 and then multiplies it byp0
, which applies the distortion to the signal.
So in summary, this equation maps the input signal
onto a piecewiselinear curve with 16 segments,
each of which has a different slope, and the
amount of distortion applied is controlled by the
p0
parameter.
3> could you show me a block diagram?
Certainly! Here's a block diagram that shows the main processing steps of the DigiShaper algorithm:
++ ++
Input signal >  Scale by 16  >  Quantization > ++
++ ++  Piecewiselinear
 Curve Mapping 
++ ++  
Drive parameter >  Reciprocal  >  Scale by 16  >  
++ ++  
 
++ ++  
Quantized value >  Cast to int  >  Scale by p0  >  Output Signal 
++ ++ ++
As you can see, the input signal is first scaled
by a factor of 16 and then quantized to an integer
value, which effectively maps it onto
a piecewiselinear curve with 16 segments. The
quantized value is then scaled back down by
a factor of 16 and multiplied by the drive
parameter p0
, which applies the distortion to
the signal. The output signal is the final result
of this processing. The reciprocal of the p0
parameter is precomputed to improve efficiency,
and SIMD instructions are used to perform the
calculations in parallel on multiple data points
at once.
4> how can we improve this algorithm?
There are several ways to improve the DigiShaper algorithm:

Use a higherorder curve: The DigiShaper algorithm uses a piecewiselinear curve with 16 segments to map the input signal. Using a higherorder curve (such as a quadratic or cubic curve) could provide a more flexible and expressive distortion effect. However, this would require more computation and memory, so it may not be feasible for some applications.

Use a more sophisticated quantization scheme: The DigiShaper algorithm uses a simple quantization scheme that rounds the scaled input signal down to the nearest integer. Using a more sophisticated quantization scheme (such as rounding to the nearest integer or using a dithering algorithm) could improve the accuracy of the curve mapping and reduce quantization noise.

Add oversampling: Oversampling is a technique that involves processing the signal at a higher sample rate than the output sample rate and then downsampling the result. This can help to reduce aliasing artifacts and improve the overall sound quality of the algorithm.

Use a nonlinear mapping: The DigiShaper algorithm uses a linear mapping to map the input signal onto the piecewiselinear curve. Using a nonlinear mapping (such as a sigmoid function or a hyperbolic tangent function) could provide a more complex and interesting distortion effect.

Implement a soft clipping function: The DigiShaper algorithm uses hard clipping, which can produce harsh and unpleasant distortion artifacts. Implementing a soft clipping function (such as a tanh function) could provide a smoother and more musical distortion effect.

Add a postfilter: The DigiShaper algorithm does not include any postprocessing filtering, which can help to shape the spectral content of the output signal and reduce noise. Adding a postfilter (such as a lowpass or highpass filter) could improve the overall sound quality of the algorithm.
These are just a few ideas for improving the DigiShaper algorithm. Ultimately, the choice of improvements will depend on the specific application and the desired sound quality.
5> coul dyou explain what you mean about the piecewiselinear curve mapping? i'm not so sure i see how this equation actually does that
Sure! The DigiShaper algorithm maps the input signal onto a piecewiselinear curve with 16 segments, each of which has a different slope. The purpose of this mapping is to introduce distortion into the signal by selectively amplifying or attenuating different parts of the signal.
The equation used in the DigiShaper algorithm achieves this mapping by quantizing the input signal to one of 16 discrete levels, which correspond to the 16 segments of the piecewiselinear curve. Each segment of the curve has a different slope, which determines how much the input signal is amplified or attenuated in that segment.
Here's a simplified example to illustrate how this works. Suppose we have a piecewiselinear curve with three segments, each of which has a different slope:
/\
/ \
/ \
/ \
/________\
If we want to map an input signal onto this curve, we can first scale the input signal by a factor of 3 to make it cover the same range as the curve:
Input signal
/\
/ \
/ \
/ \
/________\
Curve range
Next, we quantize the scaled input signal to one of three discrete levels, which correspond to the three segments of the curve:
Level 1
/\
/ \
/ \
/ \
/________\
Level 2
/\
/ \
/ \
/ \
/________\
Level 3
/\
/ \
/ \
/ \
/________\
Finally, we apply the slope of the segment corresponding to the quantized level to the input signal:
Level 1 Level 2 Level 3
/\ /\ /\
/ \ / \ / \
/ \ / \ / \
/ \ / \ / \
/________\/________\/________\
In the DigiShaper algorithm, the quantization and slope calculation are performed using the equation we looked at earlier:
(double)((int)((double)(x*p0inv*16.f+1.0)))*p0*0.0625f;
The multiplication by p0
at the end of the
equation determines the slope of the segment, and
the quantization is achieved by casting the result
to an integer and then back to a float.
I hope that clarifies how the DigiShaper algorithm maps the input signal onto a piecewiselinear curve!
6> this doesn't sound right, seems like nonsense
I apologize if my explanation was unclear or confusing. Let me try to explain again in a different way.
The DigiShaper algorithm maps the input signal onto a piecewiselinear curve by quantizing the signal to one of 16 discrete levels and then multiplying it by a slope factor. The equation used to achieve this mapping is:
(double)((int)((double)(x*p0inv*16.f+1.0)))*p0*0.0625f;
Let's break this down step by step:
 Scale the input signal by a factor of 16 and
p0inv
:
x * p0inv * 16.f
This scales the input signal so that it covers the
range [0, 16], and then scales it further by
p0inv
. The p0inv
factor determines the amount
of distortion applied to the signal.
 Add 1.0 to the scaled input signal:
x * p0inv * 16.f + 1.0
This shifts the scaled input signal up by 1.0, so that it covers the range [1, 17] instead of [0, 16].
This is done because the next step involves quantizing the signal to integer values, and adding 1.0 ensures that the quantized values are in the range [1, 16] instead of [0, 15].
 Cast the result to an integer and then back to a float:
(double)((int)(...))
This quantizes the shifted input signal to one of 16 discrete levels by rounding it down to the nearest integer. The resulting integer is then cast back to a float.
 Multiply the quantized value by
p0
and a scaling factor:
((double)((int)(...)))*p0*0.0625f
This applies the distortion to the signal by
multiplying the quantized value by p0
and
a scaling factor of 0.0625f
. The scaling factor
is used to scale the quantized value back down to
the range [0, 1].
The resulting value is the output signal for that
input sample, after being mapped onto
a piecewiselinear curve with 16 segments, each of
which has a different slope determined by p0
.
I hope this explanation is clearer and more accurate. Please let me know if you have any further questions!
7> oh, ok so this is like a 16 level quantization function
Yes, that's correct! The DigiShaper algorithm
effectively quantizes the input signal to one of
16 discrete levels using a piecewiselinear curve
with 16 segments, each of which has a different
slope determined by the p0
parameter. The
quantization is performed by rounding the shifted
input signal down to the nearest integer using
a cast to an integer data type, and then scaling
the quantized value back down by a factor of 16
and multiplying it by p0
to apply the
distortion.
8> and the drive function is a sort of amplifier for the input signal into this quantization circuit... is that correct?
Yes, that's a good way to think about it! The p0
parameter (which is referred to as the "drive"
parameter in the DigiShaper algorithm) controls
the amount of distortion applied to the input
signal. When p0
is set to a high value, the
distortion effect will be more pronounced, and
when p0
is set to a low value, the effect will
be more subtle.
The p0
parameter acts as an amplifier in the
sense that it amplifies the input signal into the
quantization circuit by scaling the quantized
value back up by a factor of p0
. This has the
effect of boosting the signal amplitude and adding
harmonic content to the signal, which results in
the distortion effect.
9> what were those triangles you were drawing up there?
I apologize if the triangle diagrams were confusing! They were meant to illustrate the piecewiselinear curve that the DigiShaper algorithm maps the input signal onto.
In a piecewiselinear curve, the curve is defined by a series of line segments with different slopes, as shown in the following diagram:
/\
/ \
/ \
/ \
/________\
Each segment of the curve has a different slope, which determines how much the input signal is amplified or attenuated in that segment.
The triangle diagrams were meant to represent this kind of curve, with each triangle representing a line segment of the curve. The size and slope of the triangles would correspond to the size and slope of the line segments in the piecewiselinear curve.
I hope that clarifies what the triangle diagrams were meant to represent!
10> thanks! could you show me how to implement a quantizer circuit in system verilog?
Certainly! Here's an example implementation of a simple quantizer circuit in SystemVerilog:
module quantizer(input logic signed [15:0] in, input logic [3:0] levels,
output logic signed [15:0] out);
// Quantize the input signal to one of the discrete levels
always_comb begin
case (in)
0: out = levels[0];
1: out = levels[1];
2: out = levels[2];
3: out = levels[3];
4: out = levels[4];
5: out = levels[5];
6: out = levels[6];
7: out = levels[7];
8: out = levels[8];
9: out = levels[9];
10: out = levels[10];
11: out = levels[11];
12: out = levels[12];
13: out = levels[13];
14: out = levels[14];
default: out = levels[15];
endcase
end
endmodule
In this implementation, the in
input is a signed
16bit signal that represents the input signal to
be quantized. The levels
input is a 4bit signal
that represents the 16 discrete levels that the
input signal can be quantized to. The out
output
is a signed 16bit signal that represents the
quantized output signal.
The quantization process is implemented using
a case
statement that checks the value of the
input signal and assigns the corresponding level
to the output signal. The default case assigns the
highest level to the output signal for any input
values that are outside the range of the defined
levels.
Note that this is a very basic implementation of a quantizer circuit, and it only supports quantization to a fixed set of levels. In practice, more sophisticated quantization schemes (such as rounding to the nearest integer or using a dithering algorithm) may be needed to achieve the desired level of accuracy and reduce quantization noise.
Dependencies
~12–22MB
~304K SLoC