Simple libraries to use in LuaJIT (and, for example, protoplug). Included are a couple tests that run on import.
I mostly checked these in Try It Online and protoplug (Sintel's fork, though it shouldn't differ on the internal side of things at all).
Rudimentary complex number support: many elementary functions such as trigonometry or general-purpose exp and log, aren't implemented. Only what was deemed to be useful in signal processing at the time was added.
cplx = require("path/to/libraries/cplx_05deg")
z1 = cplx.new(2, 3) -- z1 = 2 + 3i
z2 = cplx.expi(math.pi / 3) -- z2 = exp(iπ/3), "phasor"
-- components
print(z1.re, z1.im)
-- complex arithmetic + - * /
print(z1 + z2, z1 - z2, -z1, z1 * z2, z1 / z2)
-- raise to a real power ^
-- check near-equality
print(cplx.isclose(z2 ^ 3, cplx.new(-1)))
-- check near-equality of real numbers (for completeness)
print(cplx.isclose(math.sin(math.pi * 10000), 0))
There are componentwise comparisons z1 == z2
, z1 ~= z2
but they're as useless as for regular floats. Nonetheless when you really need them, here you go.
You can stringify with tostring(z)
.
Geometric operations with argument (phase) and absolute value:
z:conj() : cplx
conjugate,z:arg() : num
argument,z:norm() : num
norm (absolute value),z:sqr_norm() : num
norm squared, tad more efficient than(z * z.conj()).re
,z:normalized() : cplx
the same asz / cplx.new(z:norm())
but more efficient.
Powers aside from z ^ r
:
z:sqr() : cplx
the same asz * z
but more efficient,z:sqrt() : cplx
the same asz ^ 0.5
but way more efficient (no trig involved).
Do not mix Lua numbers and these complex numbers, there are no implicit conversions. Instead, use:
cplx.new(x)
to lift the realx
into the complex domain,z.re
to get the real part of a complex number even if it's "already real",z:times_r(x)
to compute z ⋅ x where z ∈ ℂ and x ∈ ℝ,z:times_i(x = 1)
to compute z ⋅ i x where z ∈ ℂ and x ∈ ℝ.
There's no polar form constructor, use cplx.expi(phase):times_r(r)
or manual cplx.new(r * math.cos(phase), r * math.sin(phase))
.
This library doesn't work in regular Lua because it uses C hypot
function.
If you're trying to use protoplug with custom tunings, it might be handy to paste the contents of an SCL file into your playground and let somebody else parse it for you. This library does exactly that!
scala = require("path/to/libraries/scala_05deg")
scl_text = [[
! Ancient Greek Archytas Enharmonic.scl
!
Archytas' Enharmonic
7
!
28/27
16/15
4/3
3/2
14/9
8/5
2/1
]]
intervals = scala.read_scl(scl_text)
assert(#intervals == 7 and intervals[-1] == 2)
frequencies = scala.midi_freqs(intervals)
assert(#frequencies == 128)
-- ... do something with `intervals` or `frequencies`
Returns the standard middle C frequency in hertz (261.63...).
Reads an SCL-formatted string and returns a list of its intervals converted to linear domain, that is, no cent values: 3/2
gets through as 1.5
, 833.1
cents will become 1.618...
.
Consider using multiline [[
...]]
strings for less hassle.
scala.midi_freqs(scale : array[num], start : num = 60, ref : num = 60, ref_freq : num = scala.middle_c(), first_note : num = 0, last_note : num = 127) : array[num]
Converts the scale you get from the previous function (or input manually as e.g. {9/8, 5/4, 4/3, 3/2, 5/3, 15/8, 2}
) to a list of concrete frequencies for a lot of notes (the standard MIDI range 0...127 by default, but do remember Lua tables index from 1!).
Arguments start
, ref
and ref_freq
are like in KBM: start
is the note corresponding to the unison of the scale, wherever ref
is the note assigned to the frequency ref_freq
. Having them as 60, 69, 440
is the same as the defaults here, if you're using 12-note octave-repeating scales. Otherwise I treat the defaults as more universal (insofar one can treat "standard middle C" frequency as universal at all).
If you're using multi-channel extended-range retuning à la Pianoteq or Surge XT, you can specify a wider range of notes or something, via first_note
and last_note
. Though it should transpose in octaves instead of using a longer list of notes. Anyway it's there.
This library may work in regular Lua, but why...