Xpressive

Write guides and how-tos about LMMS for other members.

This short introduction to Xpressive has come from experimentation / trial and error as I could not find any documentation for Xpressive apart from the list of the variables and functions within the help section within the program. I only found Xpressive 2 weeks ago, as I had been using LMMS version 1.2.2 up to that point. However, I have instantly benefited from what this instrument can do, and I hope my notes of my understanding so far can help others.

What Is The LMMS Expressive Instrument
The Xpressive instrument within LMMS Alpha is a mathematical expression-based synthesiser. Instead of relying on traditional methods, Xpressive generates its sound directly from mathematical expressions that are user defined within the time domain.

This gives the user a greater degree of control along with the ability to generate sounds that would be difficult or impossible to achieve with conventional synthesis methods.

Time Domain Overview In Xpressive
Xpressive generate waveforms using math expressions in the time domain. The user can construct waves using up to 3 waveform samples (which are actually mathematical expressions) (W1, W2 and W3) and combine these using output waves (O1 and O2).

Variables Within Xpressive
• t – Time (in seconds).
• f – Note frequency.
• key – MIDI note number.
• bnote - Base note. By default bnote is 69 which means A4 unless the user changes it.
• srate – Sample rate.
• tempo – The song's tempo. Available only in the output (O1, O2). This I believe could be used to solve the issue where decay and attack do not match the song’s tempo in the current format which I will test soon.
• rel, trel – Release flag and release time which I have not used as of yet.
• v – Note volume.
• seed - A random value that remains consistent for the lifetime of a single wave.
• A1, A2, A3 –Knobs that can adjust the values of variables A1, A2 and A3 from -1 to 1.
I do not believe that any user created variables can be assigned.

Functions Within Xpressive (copied and pasted from internal help)
• W1, W2, W3 - As mentioned before. You can reference them only in O1 and O2.
cent(x) - Gives pow(2,x/1200), so you can multiply it with the f variable to pitch the frequency.
• semitone(x) - Gives pow(2,x/12), so you can multiply it with the f variable to pitch the frequency.
• last(n) - Gives you the last n'th evaluated sample. In O1 and O2 it keeps a whole second. Thus the argument n must be in the range [1,srate], or else, it will return 0.
• integrate(x) - Integrates x by delta t (It sums values and divides them by sample rate). If you use notes with automated frequency, you should use: sinew(integrate(f)) instead of sinew(tf)
• randv(x) - A random vector. Each cell is reference by an integer index in the range [0,231]. Each evaluation of an expression results in different random vector. Although, it remains consistent in the lifetime of a single wave. If you want a single random values you can use randv(0),randv(1)… and every reference to randv(a) will give you the same value. If you want a random wave you can use randv(t
srate). Each random value is in the range [-1,1).
• randsv(x,seed) - works exactly like randv(x), except that it lets you to select the seed manually. If you want to try different random values and make it consistent in each evaluation.
• sinew(x) - A sine wave with period of 1 (In contrast to real sine wave which have a period of 2*pi).
• trianglew(x) - A triangle wave with period of 1.
• squarew(x) - A square wave with period of 1.
• saww(x) - A saw wave with period of 1.
• clamp(min_val,x,max_val) - If x is in range of (min_val,max_val) it returns x. Otherwise if it's greater than max_val it returns max_val, else returns min_val.

• abs, sin, cos, tan, cot, asin, acos, atan, atan2, sinh, cosh, tanh, asinh, acosh, atanh, sinc, hypot, exp, log, log2, log10, logn, pow, sqrt, min, max, floor, ceil, round, trunc, frac, avg, sgn, mod, etc. are also available.
• Operands + - * / % ^ > < >= <= == != & | are also available.

Modulation Types Within Xpressive (copied and pasted from internal help)
• Amplitude Modulation - W1(tf)(1+W2(tf))
• Ring Modulation - W1(t * f)
W2(t * f)
• Mix Modulation - 0.5( W1(t * f) + W2(t * f) )
• Frequency Modulation - [vol1]
W1( integrate( f + srate[vol2]W2( integrate(f) ) ) )
• Phase Modulation - [vol1]W1( integrate(f) + [vol2]W2( integrate(f) ) )

Example Basic Time Domain Signal In Expressive
The values for the periodic signal below should be changed and are only used below as an example.
Example: 0.422cos(12pit+4)
0.422 = Amplitude scaling factor.
cos = waveform type (it could be cosine, sine, square etc. refer to functions).
1 = Frequency multiplier.
2*pi = Radian measure of full circle.
t = time (keep this variable as t).
4 = phase.
The user can perform operations such as subtraction, multiplication and addition of basic waveforms.

Using W1 Or O1
The W sections are for defining the shape of a waveform over a single cycle
.
O1 and O2 sections are where the user can define the final audio output. O1 and O2 can be matched to left or right or both centred. Either or both can be used. If a W is used, at least one of the Os should reference to the W.

In some cases, such as drums where there is a low frequency component and the drum does not have to be pitch mapped to keys, it is more effective to create the drum in O1.

W1(tf) Or W1(integrate(f)) Reference In O
W1(t
f) is good if frequency is constant. Otherwise, this has created some interesting harmonics.

W1(integrate(f)) accumulates pitch over time and produces smoother transitions.

Approximating Waveforms Using The Fourier Series
What Is Fourier Series? The understanding that any periodic waveform can be built from sines and cosines. The more harmonics, the closer the approximation.

We can use Fourier analysis or approximations from studying visual frequency domain graphs to construct waves in W1 W2 or W3.

Example: W1 = 0.5cos(12pit) + 0.3sin(22pit) + 0.2sin(32pit)
We can use MATLAB or Audacity to aid in our analysis.
Octave is open source and can run most MATLAB scripts.
• MATLAB: fft(x)
• Audacity: Analyze > Plot Spectrum

MATLAB can analyse harmonic content of waveforms or the harmonic content can be visualised through Audacity.
Patch Example: NitroDeluxe - Inspired by Inner City Good Life Approximation
W1 = 0.422cos(12pit) + 0.101sin(12pit)

  • 0.126cos(22pit) + 0.114sin(22pit)
  • 0.065cos(32pit) + 0.017sin(32pit)
  • 0.022cos(42pit) - 0.069sin(42pit)
  • 0.006cos(52pit) + 0.005sin(52pit)
  • 0.006cos(62pit) - 0.010sin(62pit)
  • 0.010cos(72pit) + 0.010sin(72pit)
  • 0.011cos(82pit) + 0.014sin(82pit)+ saww(t)
    W2 = sinew(0.5 * t) + 0.5 * sinew(0.25 * t)
    O1 = W1(integrate(f) + 0.3 * W2(integrate(f))) * (1 - rel) * exp(-t * A2*2)
    The patch uses additive design of sums of sines and cosines along with a saw for additional harmonics along with modulation + envelope shaping.

The saw was added additionally. This is an example of building an approximation using a series of sines and cosines. However, in this case, Fourier analysis was not used. This was done by ear and in fact not checked against visualisation in the frequency domain.

This was included to put forward the idea that sometimes creativity and individualism can be just as important as getting it exact in sound design. Inspiration can drive new similar but interesting timbres.

Pulse Width Modulation (PWM)
Pulse Width Modulation lets the user change the width of a square wave over time adding character and motion to the sound.
Example: O1=clamp(-1,sgn(mod(t, 1/f) < (A1 / f)) * 2 - 1, 1)
The above controls the pulse width by knob A1.

Example: O1=clamp(-1,sgn(mod(t, 1/f) < (clamp((sinew(A1 * 0.5 * t))2, 0.05, 0.95) / f) - 0.0001) * 2 - 1,1)
The above controls the pulse width by an LFO (speed is controlled by A1).

ADSR Envelopes and Exponential Decay
Instead of using the inbuilt ADSR envelope within LMMS, the user can create ADSR style envelope design using conditional expressions and exponential curves within Xpressive.

Exponential Decay
exp(-t * 4)
O1 = exp(-t * 4)* sinew(t * f)
This provides a natural decay shape in this case to a sine wave, or to any other waveform.

ADSR Envelope Approximation
O1=env = (t < 0.01)(t/0.01) +
(t >= 0.01 & t < 0.1)
(1 - (t-0.01)/0.090.3) +
(t >= 0.1 & t < 0.5)
0.7 +
(t >= 0.5)(0.7exp(-(t-0.5)*10))
Attack: 10 ms linear
Decay: to 70% over 90 ms
Sustain: constant until release
Release: exponential fade

Clamping Values
The clamp(min, x, max) function restricts x to a given range.
• If x is between min and max, it returns x
• If x < min, it returns min
• If x > max, it returns max
• Example: O1: clamp(0, t * 5, 1)

Matching Waveforms To Keys
Example: O1: (key < 60) * W1((integrate(f))) +
(key >= 60 & key < 72) * W2((integrate(f))) +
(key >= 72) * W3((integrate(f)))
W1 has been mapped to the 3rd Octave and below, W2 to the 4th Octave, and W5 to the 5th Octave and above.

Experimental Techniques
Due to the limited documentation for Xpressive, I conducted several tests to understand the capability of the instrument. Two tests involved approximating sampled speech.

Fourier-Decomposed Waveform from MATLAB ("Testing12")
A waveform was analysed using MATLAB's FFT (Fast Fourier Transform) over a number of time segments where the greatest 4 harmonics in amplitude in each frame were provided for Xpressive. Two versions were created, one using sine/cosine pairs and one using sine with phase offset. 36 time frames were created, spanning 1.6 seconds. The CPU monitor went into the red on each version. At 8 harmonics the sound could not be played in real time, but could be rendered as an MP3 to play. I retained my rough MATLAB code but deemed the method not an option due to sound quality (4 harmonics), low resolution (36 frames) and CPU load.
The patch was saved in LMMS SharingPlatform as "Testing12" for future reference.

PCM Sample Playback (“Expression”)
I had previously heard Commodore 64 SIDs with spoken voices. I decided to create a MATLAB script to analyse a short waveform and perform Pulse Code Modulation at 4 bits to approximate the waveform. I was required to continually trim my sample until I got this to work once without crashing LMMS. I retained the MATLAB script, but it requires further work. The patch was saved within the project “Expression” within the LMMS SharingPlatform. Although the ‘sample’ is 4 bits, this resulted in one of the largest files I had created in LMMS.

Suggested Methodology For Using W1 To Recreate a Timbre

  1. Find loop point within a sampled instrument and analyse harmonic content of source.
  2. Design W1 as your custom waveform using Fourier or procedural shapes.
  3. Modulate with W2 or W3 (as LFO or FM).
  4. Use A1, A2 and / or A3 for modulation controls
  5. Route to O1 or O2, multiply by envelope and volume.
  6. Test in LMMS and analyse spectrum.

Improvement Ideas
Ability to provide text string for knobs in script so that user knows what each knob does.
Ability to include comments in scripts.
Currently if an instrument is double clicked in the browser, it ends up in the Beat/Baseline step sequencer without the user knowing if unchecked.
Ability to perform convolution to create filters would be of benefit.

Case Studies
Kick Drum
O1 = sinew(t * f * exp(-t * 60)) * exp(-t * 10)
Exponential decay for amplitude shaping.

Snare Drum
O1 = (randsv(t*srate, 0) * exp(-t * 70) * (t < 0.04)) +
(sinew(integrate(200)) * exp(-t * 50))
Layer of noise.
Very short decaying bursts.

Hi-Hat
O1 = clamp(-1, randv(t*srate) / (1000 * (t2) + 0.1) * (t > 0.01), 1) * (1 - t)3
Decaying random noise.

Saw Modulated by Square Stab
Sharp and harmonic-rich with envelope control.
W1 = saww(t)
W2 = 0.5 * squarew(t*4)
O1 = (W1(integrate(f)) + W2(integrate(f))) * clamp(0, t * 50, 1) * exp(-t * 5)
Harmonic-rich saw wave base.
Fast attack with exponential decay.

ewanpettigrew wrote:
Mon May 12, 2025 2:09 pm

I have instantly benefited from what this instrument can do, and I hope my notes of my understanding so far can help others

Great tutorial!

-And also since you are new in Forum
Welcome ewanpettigrew !
Here are all important links:
http://lmms.io/forum/viewtopic.php?f=1&t=4740
-A few rules and useful forum instructions
If you like to introduce yourself, to the community, go here:
http://lmms.io/forum/viewtopic.php?f=4&t=4480

Thanks Musikbear. I would like to write a proper guide to Xpressive at some stage. For now though, I will add to my notes.

Delay
At 44.1kHz last(4410) equals a 100 milliseconds delay.
Example: O1: squarew(integrate(f)) * exp(-t * 5) + 0.7 * last(1) * (t > 0.5)
Initially the square wave decays cleanly with no feedback.
After 0.5s the feedback loop a one sample delay is introduced at 70% volume.
This results in the original transient followed by an evolving tail.

Measuring Velocity To Make Decisions
Example: O1: (v < 0.2) * squarew(integrate(f)) + (v >= 0.2) * sinew(integrate(f))
If velocity is below a threshold, play a square wave, otherwise play a sine wave. The threshold set to 50% using 0.2 as the value to scale.

Combining Waveform + ADSR
Example 1: W1: squarew(t)
01: (W1(integrate(f))((t < 0.01)(t/0.01) +
(t >= 0.01 & t < 0.1)(1 - (t-0.01)/0.090.3) +
(t >= 0.1 & t < 0.5)0.7 +
(t >= 0.5)
(0.7exp(-(t-0.5)10))))+ (0.7 * last(9) * (t >5))
Square Wave with ADSR
Requires some more testing with combining delay.

Example 2: W1: .8(.3715sinew(6t)+.28185sinew(3t)+.5370sinew(1t)+.3162sinew(4t))
O1: w1(integrate(f))( (t < 0.0053)(t/0.0053) +
(t >= 0.0015 & t < 0.8)(1 - pow((t - 0.0015)/0.7985, 1.5)) +
(t >= 0.9)
(exp(-(t - 0.8)/0.05)))
House Organ with ADSR

.

I noticed that when copying and pasting my original notes in, the *s did not copy across in the Fourier series section. Therefore 0.126 * cos(2 * 2 * pi * t) + 0.114 * sin(2 * 2 * pi * t) became 0.126cos(22pit) + 0.114sin(22pit) and so on. I could not go back and edit the original post.

I would like to add these additional notes. As I mentioned already, there is very little documentation on Xpressive. Therefore, I hope these additional notes also assist anyone wishing to get started with Xpressive.

Dials And Buttons Within LMMS Xpressive
• A1 / A2 / A3 – Adjustable from -1 to 1. These knobs represent variables A1, A2 and A3 usable in expressions. They can be automated via automation tracks. Multiply their values by a scale factor in formulas to achieve desired modulation depth.
• PN1 – Panning for Output 1 (O1). Set to 0.0 if you're not using O2.
• PN2 – Panning for Output 2 (O2), left to right.
• Rel – Release Transition – 0 to 500ms. I have not used this.
• SMTH – Smoothness 0 to 70. Function not yet tested, likely applies smoothing to waveform or transitions.
• Waveforms – Preprogrammed waveforms at fundamental frequency. Moogsaw, exponential, saw, import audio file, sine, triangle, square, random noise.
o Import audio file should be used to import waveforms and not treated as a sampler such as where long samples which can be played by MIDI notes are desired. There are a number of waveforms within the samples/waveforms folder. On analysis, one cycle of each sampled waveform is the fundamental frequency.
• Interpolate – I have not used this.
• W1 – Enter formula for W1 (generally required unless using a formula in O1 which does not call O1, for example a drum).
• W2 – Enter formula for W2 (this could be used as a modulation signal but is not limited to this purpose, not necessarily required).
• W3 – Enter formula for W3 (this could be used as a modulation signal but is not limited to this purpose, not necessarily required).
o Note, in W1 to W3, periodic waves written as an expression should only be multiplied by t (not t and f).
• O1, O2 - Can reference W1–W3 or be used standalone for procedural synthesis.
• Button to Bottom Right of Formula Box – Lights up red if there’s a syntax error in your formula.
o Note, in O1 to O2, periodic waves written as an expression should be multiplied by t and f (not just t).

Frames
When working with complex or time evolving sounds in Xpressive such as speech approximations or evolving textures you may need to simulate frames which are short segments of time based waveform data. Each frame can represent a static harmonic snapshot and by switching between them over time, you can approximate dynamic sounds.
Example: O1 = (t < 0.05) * Frame1+
(t >= 0.05 & t < 0.10) * Frame2 +
(t >= 0.10 & t < 0.15) * Frame3 and on.
We cannot, to my understanding, create user defined variables. Therefore Frame(n) is a placeholder to explain the process.

Assigning Different Sounds to Different Octaves in Xpressive
Xpressive allows the use of conditional logic in expressions, enabling different behaviours depending on the MIDI note being played. One use for this is to simulate the natural changes in timbre that occur across the range of real instruments. For example, a piano has different harmonic content, articulation and dynamics depending on the octave being played.
Example: O1 = (key < 72) * W1(integrate(f)) +
(key >= 72 & key < 84) * W2(integrate(f)) +
(key >= 84) * W3(integrate(f));
Octave 3 and below play W1, Octave 4 plays W2, Octave 5 and above plays W3. There is also the option to create more choices with the expressions embedded within the larger expression within O1 allowing more choices.

Quantising Amplitudes
The floor function quantises the amplitude of a waveform. This process is often referred to as bitcrushing in music software. Bitcrushing reduces the resolution of a signal by rounding it to a fixed number of amplitude steps, producing a lo-fi sound. The process is to scale the waveform up, floor it and scale it back down.
Example: W1: floor(sinew(t * f) * 4) / 4
The sine wave is multiplied by 4, floored, then divided by 4. This quantises the amplitude into 4 discrete positive steps and 4 discrete negative steps.
If we map the multiplier to a control knob (e.g., A1), we must ensure the resulting value is an integer. If not, the waveform will be quantised at irregular or fractional step sizes, which may produce unexpected or undesirable artifacts although in some cases, these artifacts might be musically interesting.
To show this graphically, in the following example I made the period of the sinewave .1t, which although below the fundamental frequency, stretched the waveform to fill the x axis in the waveform view.
Example: O1: floor(sinew(.1t*f) * pow(2, round(A1 * 4 + 4))) / pow(2, round(A1 * 4 + 4))
We can now see the waveform amplitude quantising and reducing in resolution by moving the dial on A1. An artefact is that the top of the waveform is being gradually cut off as the resolution decreases.

Common Errors / Troubleshooting
Red Syntax Light - Usually caused by math errors. Check parentheses and ensure functions are used correctly.
No Sound - Make sure O1 or O2 is defined, and that t, f, or v are used properly. Check if volume (v) is accidentally zero.
Distorted Sound - Could be due to output not being clamped or expression returning values outside the [-1, 1] range.
High CPU Usage – Reduce number of harmonics. Consider rendering heavy patches to audio.

Also to note : W1(integrate(t)) can also be used.

ewanpettigrew wrote:
Wed May 21, 2025 1:34 pm

I noticed that when copying.....

As you can see we have inserted everything in the Tutorial section.
Atm a big rewrite of our eBook is being done. Perhaps your observations and knowledge for this Plugin, also belong there. I will come back on that 'later' :)
Good work!

musikbear wrote:
Thu May 22, 2025 3:45 am
ewanpettigrew wrote:
Wed May 21, 2025 1:34 pm

I noticed that when copying.....

As you can see we have inserted everything in the Tutorial section.
Atm a big rewrite of our eBook is being done. Perhaps your observations and knowledge for this Plugin, also belong there. I will come back on that 'later' :)
Good work!

No worries. Please feel free to paraphrase (or copy and paste) anything which I have observed so far. I have taken the time to perform 'experiments' and analyse the results in the time and frequency domains (mostly time domain) to have a good confidence in what I have written so far. That said, I am more than happy to be corrected where I may have made any assumptions.

Making my comments free for any use is the least that I can do, as someone / some people have taken the time to write this free instrument (Xpressive) which I believe has so much potential but has been largely underutilised until now due to limited documentation.

If the timing is right, I may be able to take the time to volunteer and contribute formally.

I will continue posting my observations here and hopefully these observations are of value to anyone who wishes to utilise Xpressive.

My Method for Fourier Approximation of Sampled Chord Stabs
Forenote:
It's important to understand that while we're using the results of Fourier analysis (in the case of MATLAB, often a Fast Fourier Transform or FFT), the frequencies identified in real world sampled sounds like chord stabs will most often not be exact harmonics of a fundamental frequency. A strict Fourier series, however, applies to perfectly periodic signals and their harmonics, which are multiples of the fundamental frequency (which I should have added in my first post).

Don't be scared by the mention of "Fourier." To approximate a signal based on a series of sinusoidal harmonics, I don’t believe you have to particularly understand how to calculate the Fourier series or Fourier transform. Many tools, such as MATLAB (and its free, open-source alternative, Octave), can do the calculations for you and provide the coefficient and frequency of each term. To actually do this by hand would take months.

Prepare Audio Sample
Open Audacity.
Find a short loop of the chord stab from music you like.
Look for a section where the stab is sustained, free of exponential decay, drums, or other interfering noises or artefacts (like echoes or reverb).
Make the loop as short as possible, ideally at the fundamental frequency.
Cut out either the left or right channel.
Export the loop as a .wav file.

Perform Fourier Analysis
Use MATLAB or Octave to perform Fourier analysis on your .wav file. Many good tutorials are available online for this step which involves writing a script to perform the FFT on the .wav. There are likely many other tools on the internet to provide Fourier coefficients and frequencies for a short audio loop.

(Optional: You could potentially skip the FFT if you're manually inspecting the frequency plot in Audacity and aren't concerned with phase information. Audacity also allows you to export spectral data as a .txt file that can be opened in Excel as delimited data. To do this, use the Export button on the Frequency Analysis window. Within Excel you can order each frequency by decreasing magnitude and convert dB to amplitude. Again this will not provide phase information).

Determine how many coefficients you desire. The more coefficients the closer the approximation will be to the original sample at the cost of CPU power.

Paste your Fourier coefficients into the O1: operator in Xpressive. The format should be: coefficient * sin(2 * pi * frequency * t) + ... +…
coefficient * cos(2 * pi * frequency * t) + ...+…

Spectral Plot Analysis
Open the effects for the instrument and add Equalizer and Spectrum Analyser in LMMS Xpressive.
Open the Frequency analysis plot in Audacity (Analyze -> Plot Spectrum).
Play the instrument at the same pitch as the sample in Audacity.
Compare the frequency plot from the instrument’s spectrum analyser to the frequency analysis plot in Audacity.
Adjust the control points on the equalizer to remove frequencies that sound incorrect or create unwanted noise.
Disable the coefficient(s) from the mathematical expression which correspond to the adjusted filter control point.
Instead of deleting coefficients you may optionally, "zero them out" (e.g., 0 * coefficient * frequency) to keep the coefficient for potential future use.
Disable the equaliser once you are happy with the result.
Play the instrument, listen and compare plots until both look and sound very close. Repeat process or bring back in as needed.
Remove the equaliser and spectrum analyser from the instruments effects in LMMS Xpressive.

Rewrite for Keyboard Tracking
To enable keyboard tracking, modify each term in your Fourier approximation formula as follows;
Change each term to coefficient * sin(integrate(2 * pi * f) / fundamental_frequency * frequency)) + ....
or cos(integrate((2 * pi * f) / fundamental_frequency * frequency)) + .....
(remove the t).

If we only have sine terms, such as an odd signal or we are utilising phase such as sin(2 * pi * f + phase) we can use sinew and get rid of the 2 * pi).

I have found from my experience that this more often retains a close approximation to the original sample if we retain the modified formula in O1: instead of rewriting for W1: and referencing from O1:. As there is very limited documentation for LMMS Xpressive, I cannot give a specific answer as to why. That said, I have previously had success with other approximations using W1:.

Add Exponential Decay
To add exponential decay, multiply your entire Fourier expression by exp(-t * decay_rate), where decay_rate controls how quickly the sound fades (note I have removed the word series to avoid confusion).
Example: (fourier_expression) * exp(-t * -1).

Examples
For simplification in these examples, I have not included phase.

Example Hardcore stab 1 (play at E4 as a single note)

( 0.24 * sin(integrate((2 * pi * f) / 330 * 1980)) +
0.22 * sin(integrate((2 * pi * f) )) +
0.20 * sin(integrate((2 * pi * f) / 330 * 790)) +
0.19 * sin(integrate((2 * pi * f) / 330 * 1570)) +
0.18 * sin(integrate((2 * pi * f) / 330 * 1200)) +
0.17 * sin(integrate((2 * pi * f) / 330 * 2770)) +
0.16 * sin(integrate((2 * pi * f) / 330 * 1990)) +
0.15 * sin(integrate((2 * pi * f) / 330 * 2010)) +
0.15 * sin(integrate((2 * p i* f) / 330 * 2360)) +
0.14 * sin(integrate((2 * pi * f) / 330 * 2350)))* exp(-t *1)

Example Hardcore stab 2 (play at E3 as a single note)

(0.45 *sin(integrate((2 * pi * f) )) +
0.41 *sin(integrate((2 * pi * f) / 230 *310 )) +
0.27 *sin(integrate((2 * pi * f) / 230 *940 )) +
0.24 *sin(integrate((2 * pi * f) / 230 *920 )) +
0 * 0.23 * sinew(150t ) +
0.23 *sin(integrate((2 * pi * f) / 230 *1250 )) +
0.22 *sin(integrate((2 * pi * f) / 230 *930 )) +
0.22 *sin(integrate((2 * pi * f) / 230 *620 )) +
0.20 *sin(integrate((2 * pi * f) / 230 *1230 )) +
0.19 *sin(integrate((2 * pi * f) / 230 1220 ))) exp(-t *1)

Note in Example 2, I have "zeroed out" the 150 Hz term. All these could be reduced to sinew terms, and I chose an exponential decay that sounded "OK" to me. I could have removed the 150 Hz term, but I chose to keep it here in its original form in case I change my mind (e.g., a different pair of headphones or listening on different speakers makes something sound incorrect) and require the term in the future. I could have multiplied the exponential decay by an integer and A1 to allow automation. Also, note that if this were a perfect Fourier Series, all frequencies would be integer multiples of the fundamental frequency. However, this was an FFT of a real sample with imperfections, which gave it its character.

Also for Example 2, the /230 came from manually dividing each term by the lowest (fundamental) frequency which then became the note where if the sample was played at that note, the pitch would sound the same as the source ( (0.45 *sin(integrate((2 * pi * f) )) was originally (0.45 *sin(integrate((2 * pi * f)230 ))).

Important Note: When experimenting with formulas, sometimes "mistakes" or unexpected transpositions can lead to new sounds. A lot of progression or individualism in music comes from ‘mistakes’ so if it doesn’t sound quite right, maybe you will have made something new.

I have copied and pasted a few more notes that I have made for for Xpressive. I hope by this time explaining how I tested my expressions, I can provide a methodology for anyone else wishing to explore Xpressive to verify their own expressions.

Initially, I stated that Xpressive couldn’t filter via mathematical expressions as it couldn’t perform convolution. I may have / hope I have proved myself wrong. I really thought about the last(n) function and performed a number of experiments to learn what last(n) was capable of to fill in the gaps of knowledge. I have since been working on the implementation of a finite impulse response filter.

Basic Recursive Low-Pass Filter-
I started with the expression (A1 * W1(integrate(f))) + ((1 - A1) * last(1)) in O1: and a sine wave at the fundamental frequency in W1. Comparing the spectrum analyser output against one of the built in low pass filters. I found the plot to be quite similar with the magnitude decreasing with increasing frequency sweep. I have not been able to measure phase yet to compare it to a model low pass filter. However, I could do this manually and painstakingly with Audacity (or maybe even write a MATLAB script).

Multi Tap FIR Low-Pass Filter (6 Tap)-
I next decided to add some taps to O1: A1 * ((1/6) * (W1(integrate(f)) + last(1) + last(2) + last(3) + last(4) + last(5))) + (1 - A1) * W2(integrate(f)) which gave me a more consistent roll off on the limited observations which I made.
I compared the spectrum output of the Xpressive square wave using the above filter approximation to a built in LMMS filter effect. Both showed very similar spectra. I didn’t believe that the formula was working as intended first due to seeing spikes at the harmonics, but I turned the approximated filter on and off and could see that the magnitude was decreasing on the harmonics and also comparable to the mentioned built in low pass filter. Additionally, I attempted to approximate the unit impulse function in W1: by (t < 1/srate) * srate and reduced the note length so that only one pulse was fired. The spectrum analyser results looked much like I would expect for a low pass filter.

There is still more work to be done such as working out the best way to use the filter approximation in an instrument. This may open the door to begin working with formants to approximate speech rather than the method I am going to provide an update on next.

Fixed Speech by Pulse Code Modulation (avoiding crashes)-
I previously discussed approximating samples such as speech by pulse code modulation where I had mentioned that the method was hit and miss. I had previously “sampled” 4 bit speech at 4000 samples per second to varying results.

I never explained that the format of each ternary expression must be (unless anyone works out a more efficient way) (condition ? value_if_true : value_if_false) where all ternary expressions should have nothing after the : except for the last ternary expression which has to be a value to correct the dc offset which can be and we hope should be 0. I worked out that if we have 1060 segments, the “sample” works without crashing. However, for 1061 segments the “sample” crashes LMMS. From here, I worked out that we can nest the expressions to increase the number of expressions where I actually managed to “sample” an 8 bit 16000 Hz vocal sample. I would suggest normalising, adjusting the DC offset then filtering before converting to a PCM “sample”. We can now constantly “sample” speech without fear of crashing. However, the file size will be rather large. There would be no real benefit other than to store the “sample” within the LMMS file as .wav samples as far as I am aware of cannot be stored within the LMMS Project file.

Side Band Sliders for FM-
This is more of an experimental idea. I believe that there were FM synthesisers in the 1980s where each pair of sidebands (with the greatest magnitude) magnitude could be controlled. The programmer of Xpressive has suggested the expression for FM synthesis in the help window which is correct. However, we can additionally simulate classic FM synthesis using a Bessel-inspired additive approach. In this method, we can treat the FM output as a stack of harmonically related sine waves where each sideband’s amplitude is scaled based on an approximate modulation index.
By assigning a fixed base frequency to W1 (e.g., 110 Hz), we can treat w1(integrate(f)) as our carrier frequency and use fractions like w1 / 4 to simulate the modulator. The amplitude of each sine component (carrier, 1st, 2nd sideband etc.) can be shaped using simple expressions involving the A1 knob, which controls the modulation index. This lets us smoothly increase the richness of the timbre in real time. Although this formula doesn’t compute Bessel functions directly, we can approximate the behaviour by scaling each harmonic’s amplitude to mimic the spectral evolution of FM tones.
Example:
O1:
(1 - 0.6 * A1) * sinew(2 * pi * w1(integrate(f)) * t) +
clamp(0, 0.6 * A1, 1) * sinew(2 * pi * (w1(integrate(f)) + w1(integrate(f)) / 4) * t) +
clamp(0, 0.6 * A1, 1) * sinew(2 * pi * (w1(integrate(f)) - w1(integrate(f)) / 4) * t) +
clamp(0, 0.2 * A1 * A1, 1) * sinew(2 * pi * (w1(integrate(f)) + 2 * w1(integrate(f)) / 4) * t) +
clamp(0, 0.2 * A1 * A1, 1) * sinew(2 * pi * (w1(integrate(f)) - 2 * w1(integrate(f)) / 4) * t)

Other leanings-
W1 (and W2 and W3) can be used to store integers which can be called from O1 by W1(f).
The file size is unfortunately large at about 68kb for every instrument. I previously created a collection of instruments that I wished to share for inclusion of the next release of LMMS. It seems that the way Xpressive is coded, that each expression is converted to a “sample” when enter is pressed. This makes the files large. Even for no expression, the “sample” is padded with data. I opened a file and tried to delete the data, but the sound played garbled until I pressed enter on the expression which again was converted to a “sample” increasing the file size. I think the need to convert everything to a "sample" comes with the point that has been missed until now, that the user also has the ability to draw a waveform on screen with the mouse that is converted to and stored as a "sample". With the file size of my instruments, I wouldn’t see much hope of these being included when compared to the instruments which are already bundled with expressive.

I believe that we could consider Xpressive as an arbitrary waveform generator.

A Couple More Examples-
Bird 1: O1:
((sinew(integrate(2217.46 * (1 + 0.5 * exp(-t * 20)) * (1 + 0.05 * sinew(t * 70)))) + (0.05 * randv(t * srate) * exp(-t * 50))) * ((t < 0.005)(t/0.005) + (t >= 0.005 & t < 0.07)(1 - (t-0.005)/0.065) + (t >= 0.07)0) * (t < 0.264) +(sinew(integrate(1567.98 (1 + 0.5 * exp(-(t - 0.264) * 20)) * (1 + 0.05 * sinew((t - 0.264) * 70)))) + (0.05 * randv((t - 0.264) * srate) * exp(-(t - 0.264) * 50))) * (((t - 0.264) < 0.005)((t - 0.264)/0.005) + ((t - 0.264) >= 0.005 & (t - 0.264) < 0.07)(1 - ((t - 0.264)-0.005)/0.065) + ((t - 0.264) >= 0.07)*0) * (t >= 0.264)) * (1 - rel)

Bird 2: O1:
sinew(
integrate(
2093 * (1 + A1 * (
sin(t * (0.6 + A2 * 0.4) * 2 * pi - 0.5 * 2 * pi) * 0.7

  • sin(t * (8 + A2 * 5) * 2 * pi) * 0.2
  • sin(t * 0.2 * 2 * pi) * 0.1
    ))
    )
    )
    *
    (
    (t < 0.02) * (t / 0.02)
  • (t >= 0.02 & t < 0.1) * 1
  • (t >= 0.1) * exp(-t * (2 + A2 * 3))
    )
    *
    (1 + randsv(t * srate, 0) * exp(-t * 300) * (t < 0.03) * 0.02)
    *
    (1 - rel);

3 Operator FM Template: O1:
sinew(
integrate(f)

  • A1 * sinew(integrate(f * 2))
  • A2 * sinew(integrate(f * 3 + A3 * sinew(t * 5)))
    ) * (t < 0.02 ? t / 0.02 : 1)
  • exp(-t * 1.2)

Binary Modulation:
W1: sinew(t)
W2: (
(mod(t,1) < 1/16) * 0 +
(mod(t,1) >= 1/16) * (mod(t,1) < 2/16) * 1 +
(mod(t,1) >= 2/16) * (mod(t,1) < 3/16) * 0 +
(mod(t,1) >= 3/16) * (mod(t,1) < 4/16) * 1 +
(mod(t,1) >= 4/16) * (mod(t,1) < 5/16) * 1 +
(mod(t,1) >= 5/16) * (mod(t,1) < 6/16) * 0 +
(mod(t,1) >= 6/16) * (mod(t,1) < 7/16) * 1 +
(mod(t,1) >= 7/16) * (mod(t,1) < 8/16) * 0 +
(mod(t,1) >= 8/16) * (mod(t,1) < 9/16) * 1 +
(mod(t,1) >= 9/16) * (mod(t,1) < 10/16) * 1 +
(mod(t,1) >= 10/16) * (mod(t,1) < 11/16) * 0 +
(mod(t,1) >= 11/16) * (mod(t,1) < 12/16) * 1 +
(mod(t,1) >= 12/16) * (mod(t,1) < 13/16) * 0 +
(mod(t,1) >= 13/16) * (mod(t,1) < 14/16) * 0 +
(mod(t,1) >= 14/16) * (mod(t,1) < 15/16) * 1 +
(mod(t,1) >= 15/16) * (mod(t,1) < 1) * 0
)
O1: W1(integrate(f + A1 * 100* W2(integrate(f))))

Sine + saw
This sounds like an approximation of the popular melancholic leads on radio music. This was accidental, but goes to show that many a time by accident we can create new or even common timbres
O1: clamp(-1,
(
(0.7 + 0.3 * sinew(8A1 t)) *
(
0.7saww(A2f * t + 0.2 * sinew(A34 * t)) +
0.3
saww(A2f * t + 0.2 * sinew(A34 * t))
)
),
1)