Overtune
Overtune is a Renoise tool that has some of the features of CustomWave, but requires a little “mathematic fantasy” to control. It’s a very simple tool with advanced capabilities. Download here. Please note that since v2.5 there’s a difference in how the settings are saved, so that it is possible to have multiple Overtune samples in one instrument (and still access them). Also it’s now finally possible to select rendering resolution or which ‘base note’ is to be used. All these things are the steps necessary for development of v3. The current version is 2.5.983.
Basic workings
First: map Overtune to a key in Renoise prefs. Keep in mind what you set it to. I use Shift+E. Inside the Overtune window the following keys work:
- Shift+1: focus formula field ‘Step 1’ (Esc to back out, Enter to confirm)
- Shift+2: focus formula field ‘Step N’
- Shift+3: focus slider ‘Step #’ (step count, use arrow keys: left/right de/increase by 1, up/down double/halve the value)
- Shift+4: focus slider ‘Time #’ (basically, sample duration in cycles)
- Shift+5: toggle ‘Power’ (normalize)
- Shift+6: toggle ‘Stereo’ (stereo rendering; use C to control stereo differences)
- Shift+7: focus value field ‘BaseNt’ (base note, lower base note gives higher resolution but longer calculation time and vice versa)
If you did not come here for the explanations but for the packs, here‘s the first preset pack (#001-#145) and here‘s the second (#146-#222)
Helper functions in Overtune 2.8:
-- variables "local pi = math.pi " .. "local lowrnd_buf = 0 local lowrnd_step = 0 " .. "local lownoi_buf = 0 local lownoi_step = 0 " .. "local quantz_buf = 0 local quantz_step = 0 " .. "local sf = " .. SEMITONE_FACTOR -- basics "local sin = math.sin " .. "local cos = math.cos " .. "local tan = math.tan " .. "local asin = math.asin " .. "local acos = math.acos " .. "local atan = math.atan " .. "local sqrt = math.sqrt " .. "local max = math.max " .. "local min = math.min " .. "local mod = math.mod " .. "local rnd = function() return 2*math.random()-1 end " .. "local urnd = math.random " .. "local flr = math.floor " .. "local abs = math.abs " .. "local equ = function(x) return x end " .. -- just for shorter notation in OT formulas "local sin1 = function(t) return sin(t*2*pi) end " .. "local squ1 = function(t) return squ(t*2*pi) end " .. "local tri1 = function(t) return tri(t*2*pi) end " .. "local saw1 = function(t) return saw(t*2*pi) end " .. -- range [0..1] to [-1..1] and vice versa (ac/dc, [0..1] is good for modulating) "local un = function(x) return (x+1)/2 end " .. "local bi = function(x) return x*2-1 end " .. -- range [0..1] to [0..2pi] and vice versa "local tt = function(x) return x/2/pi end " .. "local tx = function(t) return t*2*pi end " .. -- frequency modulation (not really that good, sin(X+sin(X)) works better) --"local fm = function(c,m,a) return c*(1+m*a) end " .. -- ringmod/amplitude modulation "local am = function(c,m,a) return c*(1-a+un(m)*a) end " .. "local rm = function(c,m,a) return c*(1-a+abs(m)*a) end " .. -- semitone factor function "local nf = function(txp) return (2^(txp/12)) end " .. -- logic "local ite = function(i, t, e) if i then return t else return e end end " .. "local btoi = function(b) if b then return 1 else return 0 end end " .. "local itob = function(i) if i<=0 then return false else return true end end " .. -- other basic waveforms --"local saw = function(x) return math.mod(((x-(1/tl))+pi)/pi, 2)-1 end " .. "local saw = function(x) return 2*atan(tan(x/2))/pi end " .. "local squ = function(x) return (flr(sin(x)/2+1)*2-1) end " .. "local tri = function(x) return abs(1-mod((x+1.5*pi)/pi,2))*2-1 end " .. "local pls = function(x) return (flr(x/2+1)*2-1) end " .. -- that is: pulsify "local ltan = function(x, y) return max(min(tan(x), y), -y)/y end " .. -- limit tan -- exponential sine, saw, tri "local expsin = function(x, p) if p>1 then return (squ(x)^(1-p%2))*sin(x)^p else return squ(x)*abs(sin(x))^p end end " .. "local expsaw = function(x, p) if p>1 then return (squ(x)^(1-p%2))*saw(x)^p else return squ(x)*abs(saw(x))^p end end " .. "local exptri = function(x, p) if p>1 then return (squ(x)^(1-p%2))*tri(x)^p else return squ(x)*abs(tri(x))^p end end " .. -- sine-made (recursive formula) waveforms "local sinsin sinsin = function(x, p) if p>1 then return sinsin(sin(x), p-1) else return sin(x) end end " .. "local sinsaw sinsaw = function(x, p) if p>1 then return sin(x*p)/p+sinsaw(x, p-1) else return sin(x) end end " .. "local sftsaw sftsaw = function(x, p) if p>1 then return sin(x*p)/(2^p)+sftsaw(x, p-1) else return sin(x) end end " .. "local sinsqu sinsqu = function(x, p) if p>1 then local v = p*2-1 return sin(x*v)/v+sinsqu(x, p-1) else return sin(x) end end " .. -- awesomesines etc --"local awehelp = function() end " .. --"local awesin = function(x, d, c, rdf) end " .. --"local awesin awesin = function(x, c) if c <= 1 then return sin(x) else local pdec=(sf-1)^c return (0.2^c)*(sin(x*(1+pdec))+sin(x*(1-pdec)))+awesin(x, c-1) end end " .. --"local awesin awesin = function(x, c, d) if c <= 1 then return sin(x) else local pdec=d*(sf-1)^c return (0.5^c)*(sin(x*(1+pdec))+sin(x/(1+pdec)))+awesin(x, c-1, d) end end " .. --"local awesaw awesaw = function(x, c) if c <= 1 then return saw(x) else local pdec=(sf-1)^c return (0.2^c)*(saw(x*(1+pdec))+saw(x*(1-pdec)))+awesaw(x, c-1) end end " .. -- square root sine, saw, tri "local sqtfunhelp sqtfunhelp = function(fun, x, p) if p>1 then return sqrt(sqtfunhelp(fun, x, p-1)) else return abs(fun(x)) end end " .. "local sqtsin = function(x, p) return squ(x)*sqtfunhelp(sin, x, p) end " .. "local sqtsaw = function(x, p) return squ(x)*sqtfunhelp(saw, x, p) end " .. "local sqttri = function(x, p) return squ(x)*sqtfunhelp(tri, x, p) end " .. -- distort (clip, fold, crush, noise) functions "local shape = function(x, p) if x==0 then return x else return (x/abs(x))*abs(x)^p end end " .. "local semishape = function(x, p, z) return shape(x,p)*z + (1-z)*x end " .. "local clip = function(x, y) return (1/y)*max(min(x, y), -y) end " .. "local semiclip = function(x, y, z) return (max(min(x, y), -y)*z + (1-z)*x) end " .. "local clipp = function(x, y) return max(min(x, y), -y) end " .. "local semiclipp = function(x, y, z) return (max(min(x, y), -y)*z + (1-z)*x) end " .. "local expand = function(x, y, z, p) local a=abs(x) local s=a/x if a<(y-z) then return x end local b = (a-(y-z))/z local c = z*b^p return x+s*c end " .. "local fold = function(x, y) return -bi(abs(1-abs(un((1+y)*x)))) end " .. "local semifold = function(x, y, z) return fold(x, y)*z + (1-z)*x end " .. "local crush = function(x, y) return flr(x*y)/y end " .. "local semicrush = function(x, y, z) return (flr(x*y)/y)*z + (1-z)*x end " .. "local quant = function(x, dx) if quantz_step == 0 then quantz_buf = x end quantz_step = mod( quantz_step + 1, dx ) return quantz_buf end " .. "local semiquant = function(x, dx, z) return quant(x,dx)*z + (1-z)*x end " .. "local noise = function(x, y, p) return x+(ite(x<0, -1, 1))*y*(abs(x)^p)*rnd() end " .. -- add noise according to amp(x) and factor(y) and curve(p) "local lowrnd = function ( t, skip ) if lowrnd_step == 0 then lowrnd_buf = rnd() end lowrnd_step = mod( lowrnd_step + 1, skip ) return lowrnd_buf end " .. "local lownoise = function ( t, part ) if lownoi_step ~= flr(t*part) then lownoi_buf = rnd() end lownoi_step = flr(t*part) return lownoi_buf end " .. -- muffle "local muffle = function(x, y, z, p) local a=abs(x) local s=a/x if a<(y-z) then return x end local b = (a-(y-z))/z local c = z*(b/p)^p return x-(s*c)/p end " .. "local ubermuffle = function(x, y, z, p) local a=abs(x) local s=a/x if a<(y-z) then return x end local b = (a-(y-z))/z local c = z*(b/p)^p return x-(s*c) end " .. --"local brutalize = function(x, y, z, p) local m=min(abs(x),y-z) return (x/abs(x))*(m+z/((1/(abs(x)-m))^p)) end " .. --"local muffold = function(x, y, z, p) local a=abs(x) local s=a/x if a<(y-z) then return x end local b = (a-(y-z))/z local c = z*b^p return x-s*c end " .. -- supermin/supermax type 'clip' functions "local supermax = function(x, y) if x >= 0 then return max(x,y) else return min(x,y) end end " .. "local supermin = function(x, y) if x >= 0 then return min(x,y) else return max(x,y) end end " .. -- morph between two functions "local morph = function(x, y, z) return ((1-z)*x+z*y) end " .. "local mix = function(f1, f2, x, a) return ((1-a)*f1(x)+(a)*f2(x)) end " .. --"local mix = function(x, ztab, functab) local factor = 0 if #ztab ~= #functab then return 0 else for _,f in ztab do factor = factor + f end local res = 0 for i = 1, #ztab do print(''..i..'. type: '..type(functab[i])) if type(functab[i]) == 'function' then res = res + ztab[i] * functab[i](x) elseif (#functab[i]) == 1 then res = res + ztab[i] * functab[i][1](x) else res = res + ztab[i] * functab[i][1](x, unpack(functab[i][2])) end end return res/factor end end " .. -- unary [0..1] pulse from/to "local upft = function(x, f, t) if x < f or x >= t then return 0 else return 1 end end " .. "local upf = function(x, f) if x < f then return 0 else return 1 end end " .. "local upt = function(x, t) if x >= t then return 0 else return 1 end end " .. -- ramps "local sqrtsqrt = function(x, p) return sqtfunhelp(equ, x, p) end " .. "local ru = function(t, p) return t^p end " .. -- ramp (exp) "local rd = function(t, p) return (1-t)^p end " .. "local aru = function(t, p) return 1-(1-t)^p end " .. -- anti-ramp "local ard = function(t, p) return 1-t^p end " .. "local rru = function(t, p) return 1-sqrtsqrt(1-t, p) end " .. -- root ramp "local rrd = function(t, p) return 1-sqrtsqrt(t, p) end " .. "local raru = function(t, p) return sqrtsqrt(t, p) end " .. -- root anti-ramp "local rard = function(t, p) return sqrtsqrt(1-t, p) end " .. --"local cosu = function(t, p) return (cos((1-t)*pi)/2+.5)^p end " .. -- shaped half-cosine ramp --"local cosd = function(t, p) return (cos(t*pi)/2+.5)^p end " .. "local cosu = function(t, p) return shape((cos((1-t)*pi)/2+.5)*2-1,1/p)/2+.5 end " .. -- shaped half-cosine ramp "local cosd = function(t, p) return shape((cos(t*pi)/2+.5)*2-1,1/p)/2+.5 end " .. --"local acosu = function(t, p) return (acos((1-t)*2-1)/pi)^p end " .. --"local acosd = function(t, p) return (acos(t*2-1)/pi)^p end " .. "local acosu = function(t, p) return shape(((acos((1-t)*2-1)/pi)*2-1),p)/2+.5 end " .. "local acosd = function(t, p) return shape(((acos(t*2-1)/pi)*2-1),p)/2+.5 end " .. "local atanu = function(t, p) return (math.atan(1-(1-t)*2)/pi*2+.5)^p end " .. -- shaped atan ramp "local atand = function(t, p) return (math.atan(1-t*2)/pi*2+.5)^p end " .. "local recu = function(t, p) local q = 1/p return (p+1)*(q/((1-t)+q)-(1/(p+1)))/p end " .. "local recd = function(t, p) local q = 1/p return (p+1)*(q/(t+q)-(1/(p+1)))/p end " .. -- start later / done quicker "local sl = function(t,p) return max(t/p-1/p+1,0) end " .. -- max(T*4/3-1/3,0) "local dq = function(t,p) return min(t/p,1) end " .. -- envelope "local env = function(x, t) local y = 0 local pc local pn = nil for i = 1, #t-1 do if pn ~= nil then pc = pn else pc = t[i] end pn = t[i+1] if x < pn[1] and x >= pc[1] then if pn[3] == nil or pn[4] == nil then y = ((x-pc[1])/(pn[1]-pc[1]))*(pn[2]-pc[2])+pc[2] else y = pn[3](((x-pc[1])/(pn[1]-pc[1])),pn[4])*(pn[2]-pc[2])+pc[2] end break end end return y end " ..
There’s also a new env function that needs 2 arguments, the T var and a table of (x,y) coords. It generates a custom envelope. Every coord in the table except for the first one can also specify one of the ramp functions to modify how the lines are drawn in the final envelope. Good example usages of these are in the envelopes.xrns file.
Note: the rest of this page is still based on Overtune v1. This will soon be updated, but most of the steps hereunder are still the same.
If you hear a piano player strike the A key, it does not only play at a 440Hz frequency, but also at 660Hz, 880Hz, 1320Hz, 1760Hz, and so on. Of course, every step away from the original ‘base’ frequency normally sounds less loud. In some way the human ear captures all these resonances of multiplied frequencies and perceives them all together as one note still. To try and simulate the sounds of pianos, guitars, and many other types of instruments, synthesizer makers started experimenting with square, triangle, and sawtooth type waves. Now with Overtune, a little mathematical knowledge, and a strong will to keep trying, anyone can create their own synthesizer sound.
Starters
A single cycle of a soundwave is calculated in Overtune with the following parameters:
- Step 1 (function on X, e.g. math.sin(X))
- Step N (function on X and N, e.g. math.sin(N*X)/(2^(N-1)))
- Step # (step count, or overtone count, number that’s at least 1 and maximum 99)
this is then looped by Renoise to create a sound. If you leave the formula math.sin(X) in the first field, and then either just put a zero (0) in the second field or reduce the steps amount to the minimum (1), you can play a very pure (and very boring) synthesized sinewave-sound. However this boring sound is the base of many cool synthesizer sounds/waves.
Square
The simple thing about Overtune is that it will simply calculate the waveform for Step 1, then add Step N to it as many times as Step #. In the end the tool looks at the wavetable and normalizes it (that practically means that if you have many very low amplitude overtones the main waveform will still be visible and at max volume).. The complex or advanced thing is that, since you have every function in luas math library at your disposal, you can do many cool things with this sinewave. The default functions that the Overtune window fills in generate a kind of ‘Soft Saw’ waveform, but let’s start out with this sine and what we can do with that. For instance, let’s try and make a square wave:
- Step 1: math.floor(math.sin(X)/2+1)*2-1
- Step N: 0
- Step #: 1
Keep this one in mind (or on paper of course), the square wave is really handy for turning (e.g. modulated) math.abs() formulas back into full shape.
Saw
Or a ‘real’, full on harsh sawtooth wave:
- Step 1: math.atan(math.tan(X/2))/2
- Step N: 0
- Step #: 1
- Step 1: X/math.pi+(math.floor(math.sin(X)/2+1)*2-1)-1
- Step N: 0
- Step #: 1
Modulating / Amplifying / Multiplying
Let’s try a waveform that’s 75% procent normal sine, and 25% modulated with another frequency:
- Step 1: 0.75*math.sin(X)+0.25*math.sin(X)*math.sin(X*7)
- Step N: 0
- Step #: 1
Distorting / Clipping
Now, let’s not modulate, but distort that one instead with a triangle wave. First, grab a new instrument, launch Overtune again, and make a very easy modulate-saw:
- Step 1: math.mod(X/2/math.pi, 1)
(the other parameters will be the same as last few examples for now)
Alright that’s something.. but I want a double saw, no, actually, a triangle! No problem – you can make it out of this although the formulas are sometimes annoying to type in as fast as you can come up with them.
- Step 1: math.mod(X/math.pi, 1)*(math.floor(math.sin(X)/2+1)*2-1)-(math.floor(math.sin(X)/2+1)-1)
- Step 1: math.min(math.abs(0.75*math.sin(X)+0.25*math.sin(X)*math.sin(X*7)), math.mod(X/math.pi, 1)*(math.floor(math.sin(X)/2+1)*2-1)-(math.floor(math.sin(X)/2+1)-1))*(math.floor(math.sin(X)/2+1)*2-1)
- Step 1: math.sin(X*(1-0.25*math.sin(X)))
Groovy. Shifted saw is a little more complicated:
- Step 1: math.mod(X*(1-.25*math.sin(X/2))/math.pi, 1)+(math.floor(math.sin(X*(1-.25*math.sin(X/2)))/2+1)-1)
Bit Crush
You can also bitcrush stuff yourself with overtune. Try:
- Step 1: math.floor(math.sin(X)*31)/31
Frequency Modulation / Drums
Using the ‘phase shift’ technique from before you can also make a simple kickdrum:
- Step 1: math.sin(X*9*(X/2/math.pi))
Of course you have to set it to no looping and perhaps transpose like -11. Snare:
- Step 1: math.sin(X*9*(X/2/math.pi))*0.8+0.2*math.random()+0.1
I don’t know why the +0.1 is necessary, just is. I’ve made a simple drumkit already, compose purely out of transposed overtune renders. Of course these will be available at some point in the future as well.
Rest
Other saw-like characteristics are achievable by having every step phase invert:
- Step 1: math.sin(X)
- Step N: math.sin(N*X)*((N%2)*2-1)/2^(N-1)
- Step #: 5
Use amplification with triangle wave (I have a different folder with mod-instruments now that only have waves with values between 0 and 1 instead of -1 and 1) to make overtones stronger or in this case weaker near the middle:
- Step 1: math.sin(X)
- Step N: math.sin(N*X)*(1-((math.mod(X/math.pi,1)*(math.floor(math.sin(X)/2+1)-1))))/2^(N-1)
- Step #: 5 or 12 or whatever you like