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(tsrate). 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(tf) 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
- Find loop point within a sampled instrument and analyse harmonic content of source.
- Design W1 as your custom waveform using Fourier or procedural shapes.
- Modulate with W2 or W3 (as LFO or FM).
- Use A1, A2 and / or A3 for modulation controls
- Route to O1 or O2, multiply by envelope and volume.
- 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.