surge-lipol

Crates.iosurge-lipol
lib.rssurge-lipol
version0.2.12-alpha.0
sourcesrc
created_at2021-10-25 21:37:51.613751
updated_at2023-04-07 21:56:39.359902
descriptionsurge synthesizer -- linear interpolation struct
homepage
repositoryhttps://github.com/klebz/surge-rs
max_upload_size
id471269
size82,624
(klebs6)

documentation

README

surge-lipol

A Rust crate that implements the LiPol (linear interpolator) algorithm for audio processing.

A LiPol is a type of audio processing algorithm that linearly interpolates between two values over time, making it useful for smoothly transitioning between audio values, for example when fading in or out.

The crate includes a struct LiPol<T> that implements the LiPol algorithm. It contains the following fields:

  • v: the current audio value

  • new_v: the target audio value (used for interpolation)

  • dv: the rate of change between v and new_v

  • bs_inv: the inverse of the block size, used for calculating dv

  • first_run: a boolean flag indicating whether this is the first time the LiPol has been used (used for special first-run behavior)

The LiPol struct also includes several methods for working with the LiPol algorithm:

  • new(blocksize: usize): creates a new LiPol with the given block size

  • set_blocksize(&mut self, n: usize): sets the block size of this LiPol to n

  • instantize(&mut self): sets the current audio value to the target audio value and resets the rate of change to zero

  • process(&mut self): advances the current audio value by the current rate of change

  • get_target_value(&mut self) -> T: gets the current target audio value

  • reset(&mut self, blocksize: usize): resets this LiPol with the given block size

  • new_value(&mut self, x: T): sets the target audio value to x and calculates the new rate of change

The crate also includes some utility functions for testing and initializing the LiPol, such as default() for initializing with a default block size, and test_set_blocksize() for testing the set_blocksize() method.

Overall, surge-lipol provides a simple and efficient implementation of the LiPol algorithm in Rust, making it a useful tool for smoothly transitioning between audio values in digital audio processing applications.

Mathematical Analysis

The LIPOL algorithm is a form of linear interpolation that approximates values between two points using a straight line. Given two points (x0, y0) and (x1, y1), the LIPOL algorithm can be used to approximate a value y at a point x between x0 and x1. The formula for LIPOL is:

y = y0 + (x - x0) * ((y1 - y0) / (x1 - x0))

The surge-lipol crate provides an implementation of the LIPOL algorithm that can be used for digital signal processing in the context of the surge synthesizer system. It also includes various tests to ensure the correctness of the implementation.

Overall, the surge-lipol crate provides a powerful tool for approximating values in between two points and is useful for digital audio signal processing.

6> /file src/lipol_ps.rs

This code appears to be an implementation of a linear interpolator algorithm for audio processing, with support for both scalar and vector operations. The LiPol struct is a generic struct that stores the current audio value, the target audio value, the rate of change between the two, and other necessary variables for performing the linear interpolation. The LipolPs struct is a specialized struct that uses the SSE instruction set to perform vector operations on the audio values. It also provides additional functions for setting the target audio value in various ways and for initializing and storing blocks of line segments. Overall, the code seems to be a well-optimized implementation of a common audio processing algorithm.

7> /file src/mac.rs

This is more code from the same crate. The two functions mac_block_to and mac_2_blocks_to use the LipolPs struct to perform a multiply-accumulate operation on blocks of data.

The mac_block_to function takes a source block of f32 values at src and a destination block of f32 values at dst, and multiplies each element in the source block with the corresponding element in a line segment defined by the LipolPs struct, and then adds the result to the corresponding element in the destination block.

The mac_2_blocks_to function is similar, but takes two source blocks at src1 and src2, and two destination blocks at dst1 and dst2, and multiplies each element in the corresponding pair of source blocks with the corresponding element in a line segment defined by the LipolPs struct, and then adds the result to the corresponding element in the corresponding pair of destination blocks.

Both functions use a macro lipol_ps_sse_block! to iterate over the blocks of data and apply the operation to each element. The unsafe keyword is used to indicate that the functions may access memory in an unsafe manner, and that the caller is responsible for ensuring that the memory is valid and properly aligned. The TryInto trait is used to convert the NQ argument to a usize value, and any errors are reported using the Debug trait.

8> /file src/multiply.rs

This is another part of the crate::LipolPs module. It contains several unsafe functions that are used for multiplying and saturating blocks of f32 values using the Single Instruction Multiple Data (SIMD) instructions provided by the SSE (Streaming SIMD Extensions) instruction set.

The first function multiply_block_to multiplies a block of f32 values in the src pointer with two __m128 vectors, y1 and y2, and stores the result in the dst pointer. The multiplication is done in blocks of 2.

The second function multiply_2_blocks_to multiplies two blocks of f32 values in the src1 and src2 pointers with two __m128 vectors, y1 and y2, and stores the result in the dst1 and dst2 pointers. The multiplication is done in blocks of 2.

The third function multiply_2_blocks multiplies two blocks of f32 values in the src1 and src2 pointers with two __m128 vectors, y1 and y2. The multiplication is done in blocks of 2.

The fourth function multiply_block multiplies a block of f32 values in the src pointer with two __m128 vectors, y1 and y2. The multiplication is done in blocks of 8.

The fifth function multiply_block_sat1 saturates the result of the multiplication to a maximum value of 1.0. The multiplication is done in blocks of 8.

9> /file src/sse_block.rs

This is a Rust macro definition that generates code for performing a specific calculation using SIMD instructions. The macro is named lipol_ps_sse_block, and it takes four arguments: an identifier $self, an expression $nquads, an expression $n, and a code block $code.

The macro generates code that performs a loop over the range 0..$nquads or 0..($nquads << 2) (depending on the value of $n). The loop body executes the $code block, passing it four arguments: an index i, and three mutable 128-bit SIMD vectors of floating-point numbers named y1, y2, and dy.

The y1 and dy variables are initialized to zero using the z128![] macro, and then initialized with appropriate values for the specific calculation being performed using the initblock method on the $self object.

The y2 variable is computed by adding y1 and dy element-wise using the _mm_add_ps function. The dy variable is updated by multiplying it by a constant value defined using the m128_two![] macro.

The $code block contains the actual computation being performed using SIMD instructions./f It uses the _mm_addi_ps and _mm_mul_ps functions to performle addition and multiplication of 128-bit SIMD vectors of floating-point numbers, respectively.

10> /file src/trixpan.rs

This code defines the implementation of several methods for the LipolPs struct.

The trixpan_left and trixpan_right methods perform a specific panning calculation on two input channels, which is used in the trixpan_blocks method. These methods use SIMD instructions to perform the calculations.

The trixpan_blocks_do_quad method takes pointers to left and right channels, pointers to output left and right channels, a mutable reference to a SIMD vector y, and a mutable reference to a SIMD vector dy. It uses trixpan_left and trixpan_right to calculate the left and right output channels for a quad of samples, and updates y and dy with the new values for the next quad of samples.

The trixpan_blocks method takes pointers to left and right channels, pointers to output left and right channels, and a number of quads to process. It initializes y and dy, and then calls trixpan_blocks_do_quad for each quad of samples, passing the appropriate pointers and references.

The code makes use of several macros defined in the ix module, including z128!, m128_one!, and m128_two!, which initialize SIMD vectors to zero, one, and two, respectively. It also defines the lipol_ps_sse_block macro, which generates code that performs a specific calculation using SIMD instructions.

11> /file src/fade.rs

These two unsafe methods belong to the LipolPs struct implementation in Rust, and they are used for fading blocks of audio data. Both methods take a pointer to the source buffer(s), a pointer to the destination buffer(s), and an integer nquads that specifies the number of quads (groups of four f32 values) to process. The methods use SSE intrinsics to perform the fades, which allows for parallel processing of the data using SIMD instructions.

The first method, fade_block_to, takes two source buffers src1 and src2 and fades them into the destination buffer dst. The fade is based on the current state of the LipolPs object, which determines the fading parameters. The method is unsafe because the user needs to make sure that the input buffers have at least nquads elements, and the function can safely access them.

The second method, fade_2_blocks_to, is similar to the first one, but it takes four source buffers src11, src12, src21, and src22, and fades them into two destination buffers dst1 and dst2. The method performs two fades in parallel, writing the results to dst1 and dst2 respectively. Like the first method, this method is also unsafe, and the user needs to make sure that the input buffers have at least nquads elements and the function can safely access them.

Both methods use a lipol_ps_sse_block! macro, which provides a loop over the data that needs to be processed, taking into account the fact that SSE instructions operate on 128-bit registers, and therefore the data needs to be processed in 4-byte (32-bit) chunks. The methods also use the _mm_mul_ps and _mm_add_ps functions, which are intrinsic functions that allow for low-level manipulation of 128-bit SSE registers.

12> /file src/add.rs

This is the implementation of two methods for the LipolPs struct: add_block and subtract_block. Both of them take a pointer to a memory region and the number of quadwords to operate on, and use vectorized SSE instructions to perform the corresponding operation on the quadwords in the memory region.

Both methods are marked as unsafe because the caller is responsible for ensuring that the memory pointed to by the pointer is valid and that the quadwords can be accessed contiguously. Additionally, the pointer must be a valid pointer to a mutable slice of f32 values.

The add_block method adds the given block of quadwords to the LipolPs instance, while the subtract_block method subtracts the corresponding values from the current state for a block of quadwords in the memory region.

These methods use a macro called lipol_ps_sse_block! which generates SSE2 vector code that operates on a block of quadwords. The macro takes a closure which operates on four references to __m128 values, which are used to hold the current state of the LipolPs instance. The closure also takes an index value, which is used to access the quadwords in the memory region.

Overall, these methods provide efficient vectorized operations on blocks of quadwords, making use of the SSE2 instructions provided by modern CPUs.

4> thanks! could you teach me how a linear interpolator works?

Sure! I'd be happy to explain how a linear interpolator works.

Linear interpolation is a method of approximating values in between two known points by drawing a straight line between them. The basic idea is to use the slope of the line connecting the two points to estimate the value at a point between them. The closer the point is to one of the known points, the closer the approximation will be to the actual value at that point.

In the context of digital signal processing, linear interpolation is often used to increase the sample rate of a signal. For example, if a signal is sampled at a rate of 44100 samples per second, but we need to process it at a rate of 48000 samples per second, we can use linear interpolation to estimate the values of the signal at the extra sample points. We would take two adjacent samples of the signal and draw a straight line between them. Then we would use the slope of that line to estimate the value of the signal at the extra sample points.

To illustrate this, let's say we have a signal with two samples: x0 = 1 and x1 = 5, and their corresponding values: y0 = 2 and y1 = 4. If we want to estimate the value of the signal at a point x = 2.5, we can use the LIPOL formula:

y = y0 + (x - x0) * ((y1 - y0) / (x1 - x0))

Plugging in the values, we get:

y = 2 + (2.5 - 1) * ((4 - 2) / (5 - 1))
  = 2 + 1.5 * (2 / 4)
  = 2.75

So the estimated value of the signal at x = 2.5 is 2.75.

Of course, this is just a simple example. In practice, we would use many more samples to interpolate the signal and reduce the error in the estimation. Additionally, there are other interpolation methods such as cubic interpolation that can be used to further improve the accuracy of the estimation.

5> which methods do we have for linear interpolation?

Linear interpolation involves approximating values in between two known points by drawing a straight line between them. There is only one formula for linear interpolation, which is commonly referred to as the "LIPOL" formula. LIPOL stands for "linear polar" and is also known as the "lerp" formula or the "lerp" function.

The LIPOL formula is:

y = y0 + (x - x0) * ((y1 - y0) / (x1 - x0))

where x0 and y0 are the coordinates of the first known point, x1 and y1 are the coordinates of the second known point, x is the x-coordinate of the point we want to approximate, and y is the estimated value of the point.

While there is only one formula for linear interpolation, there are different ways to implement it in code. Some programming languages provide built-in functions or libraries for linear interpolation, while others require manual implementation. In general, linear interpolation is a simple and computationally efficient method for approximating values in between two known points, and is widely used in digital signal processing and computer graphics.

6> which instructions do we use for this on arm?

On ARM processors, the instructions used for linear interpolation depend on the specific architecture and instruction set. However, the most common instruction used for linear interpolation on ARM is the FMA (Fused Multiply-Add) instruction. The FMA instruction is a single instruction that performs a multiplication and addition in one step, which can improve performance in some cases.

In particular, the ARM NEON instruction set provides a set of SIMD (Single Instruction Multiple Data) instructions that can be used for linear interpolation. These instructions operate on multiple data elements in parallel, which can be useful for processing large blocks of audio or video data.

The NEON instruction set includes instructions for loading and storing data from memory, as well as arithmetic and logical operations on vectors of data. To perform linear interpolation using NEON instructions, you would typically load the input data into a vector register, apply the LIPOL formula to the vector elements, and then store the output data back to memory.

Here's an example of how you might use NEON instructions to perform linear interpolation in ARM assembly language:

// Load input data into vector registers
vld1.32 {d0}, [r0]  // Load x0 and y0 into d0
vld1.32 {d1}, [r1]  // Load x1 and y1 into d1
vld1.32 {d2}, [r2]  // Load x values into d2

// Compute slope and delta x
vsub.f32 d3, d1, d0  // Compute delta y (y1 - y0) in d3
vsub.f32 d4, d2, d0  // Compute delta x (x - x0) in d4
vdiv.f32 d5, d3, d4  // Compute slope (delta y / delta x) in d5

// Compute interpolated values
vsub.f32 d6, d2, d0  // Compute delta x (x - x0) in d6
vmul.f32 d7, d6, d5  // Multiply delta x by slope to get delta y (d7)
vadd.f32 d8, d7, d0  // Add delta y to y0 to get interpolated value (d8)

// Store output data to memory
vst1.32 {d8}, [r3]  // Store interpolated values back to memory

This example loads the input data into vector registers using the vld1 instruction, computes the slope and delta x using the vsub and vdiv instructions, computes the interpolated values using the vmul and vadd instructions, and stores the output data back to memory using the vst1 instruction. This code assumes that the input and output data are 32-bit floating-point values, and that the input data is stored in memory at addresses r0, r1, and r2, and that the output data is stored in memory at address r3.

7> what are some caveats when we do linear interpolation? are there better forms of interpolation for certain tasks?

While linear interpolation is a simple and useful technique, there are some caveats to be aware of:

  1. Linear interpolation assumes a straight line between the two known points. If the function being interpolated is not linear, the approximation may be inaccurate, especially for values far away from the known points.

  2. Linear interpolation can introduce artifacts such as "aliasing" in digital signal processing. Aliasing occurs when the signal being interpolated contains high-frequency components that are not properly filtered, resulting in distortion or noise in the output.

  3. Linear interpolation can also introduce "overshoot" or "undershoot" errors, especially if the input signal has discontinuities or sharp changes in slope. Overshoot or undershoot errors occur when the approximation exceeds the actual value or falls short of it, respectively.

To address these issues, there are several other interpolation methods that may be more appropriate for certain tasks:

  1. Piecewise linear interpolation: This method involves dividing the input range into smaller intervals and performing linear interpolation separately for each interval. This can reduce the error in the approximation and better capture the behavior of nonlinear functions.

  2. Polynomial interpolation: This method involves fitting a polynomial of degree n to the n+1 known points, and using the polynomial to approximate values in between them. This can provide a more accurate approximation than linear interpolation, especially for functions with higher degrees of curvature.

  3. Cubic spline interpolation: This method involves fitting a piecewise cubic polynomial to the known points, such that the function and its first and second derivatives are continuous across the interval. This can provide a smooth and accurate approximation, and is commonly used in computer graphics and animation.

  4. Fourier series interpolation: This method involves approximating the function as a sum of sine and cosine waves of varying frequency, and using the Fourier coefficients to estimate values in between the known points. This can provide a highly accurate approximation for periodic functions, but may be less suitable for non-periodic functions.

Overall, the choice of interpolation method depends on the specific task and the characteristics of the function being approximated. Linear interpolation is a useful and simple method, but may not always be the best choice for every situation.

8> could you describe how each of these four methods applies to the domain of audio?

Sure, I'd be happy to describe how each of these four interpolation methods applies to the domain of audio.

  1. Piecewise linear interpolation: This method is commonly used in audio signal processing for up-sampling (increasing the sampling rate of a signal). In this context, the input signal is divided into small intervals, and linear interpolation is used to estimate the values of the signal at the extra sample points. Piecewise linear interpolation is simple and computationally efficient, and can provide a reasonable approximation of the original signal at a higher sampling rate.

  2. Polynomial interpolation: This method is less commonly used in audio signal processing due to its tendency to introduce "ringing" artifacts in the output signal. Ringing occurs when the polynomial overshoots or undershoots the actual signal, resulting in oscillations or "echoes" in the output. However, polynomial interpolation can be useful for certain tasks such as frequency analysis or spectral modeling, where a smooth and accurate representation of the signal is desired.

  3. Cubic spline interpolation: This method is commonly used in audio signal processing for a variety of tasks such as pitch shifting, time stretching, and resampling. In these contexts, cubic spline interpolation can provide a smooth and accurate approximation of the original signal, while preserving important features such as transients and harmonics. Cubic spline interpolation can also be used for waveform shaping and envelope generation, where a smooth and continuous curve is desired.

  4. Fourier series interpolation: This method is commonly used in audio signal processing for waveform synthesis and resynthesis. In this context, the Fourier series coefficients of a waveform are computed, and used to synthesize or resynthesize the waveform at different frequencies or sampling rates. Fourier series interpolation can provide a highly accurate representation of the original waveform, especially for periodic signals, and can be used for tasks such as additive synthesis, frequency modulation, and wavetable synthesis.

Overall, the choice of interpolation method in audio signal processing depends on the specific task and the desired characteristics of the output signal. While piecewise linear interpolation is simple and computationally efficient, other methods such as cubic spline and Fourier series interpolation can provide a more accurate and versatile representation of the original signal.

Commit count: 135

cargo fmt