ckv language and ugen reference

Shortcuts: ugen reference, language reference

This page is for users who are familiar with the language. Potential users who don't quite know what they're doing yet should go through the tutorial first.

ugen Reference

Noisemakers Filters
Analysis Utility
Custom

Noisemakers

Noise

Generates random noise.

connect(Noise(), speaker);
noise = Noise()
Constructs a Noise
noise.last
The last sample generated by noise

SawOsc

Generates a sawtooth wave.

sawosc = SawOsc(440);
connect(sawosc, speaker);
sawosc.phase = 0.5;
sawosc = SawOsc(freq)
Constructs a SawOsc at the given frequency
sawosc.last
The last sample generated by sawosc
sawosc.phase
Phase of sawosc, between 0 and 1

SinOsc

Generates a sine wave.

sinosc = SinOsc(440);
connect(sinosc, speaker);
sinosc.phase = 0.5;
sinosc = SinOsc(freq)
Constructs a SinOsc at the given frequency
sinosc.last
The last sample generated by sinosc
sinosc.phase
Phase of sinosc, between 0 and 1

SndIn

Plays audio from a file. Uses ffmpeg, so it can decode many formats.

play("audio.mp3", speaker);
sound = SndIn("audio2.mp3");
if sound == nil then
  print("could not load audio2.mp3");
else
  print("loaded " .. sound.filename);
  print("length in seconds:", sound.duration / second);
end
play(filename, dest)
Creates a SndIn, loads the given file, and begins playing in its own thread. dest is optional and defaults to speaker.
sound = SndIn(filename)
Constructs a SndIn with the given file. If there is an error loading the file, sound is nil.
sound.last
The last sample generated by sound
sound.filename (read-only)
Path to the file being played
sound.duration (read-only)
Duration of file being played

SqrOsc

Generates a square wave.

sqrosc = SqrOsc(440);
connect(sqrosc, speaker);
sqrosc.phase = 0.5;
sqrosc = SqrOsc(freq)
Constructs a SqrOsc at the given frequency
sqrosc.last
The last sample generated by sqrosc
sqrosc.phase
Phase of sqrosc, between 0 and 1

Filters

Delay

Passes through inputs with the given amount of delay.

connect(mic, Delay(3 * seconds), speaker);
delay = Delay(amount)
Constructs a Delay that passes through its inputs with the given amount of delay
delay.last
The last sample generated by delay

Gain

Passes input signals through scaled by the given amount.

An alias for PassThru.

PassThru

Passes input signals through scaled by the given amount.

passthru = PassThru(0.5);
connect(mic, passthru, speaker);
print("scaling mic by", passthru.gain);
passthru = PassThru(gain)
Constructs a PassThru that passes through its inputs scaled by gain
passthru.last
The last sample generated by passthru
passthru.gain
The scale factor for signals passing through

Analysis

Follwer

An envelope generator. At every sample, compares the rectified sample coming in to the Follower's last value. If the sample is bigger, output that. If not, scale its previous value such that it decays with the given half-life.

follower = Follower(0.1 * second);
connect(mic, follower, blackhole);

while yield(0.01 * seconds) do
  print(string.rep(",", follower.last * 50))
end
follower = Follower(half_life)
Constructs a Follower with the given half-life
follower.last
The last sample generated by follower
follower.decay
The amount by which the currently held value is decayed every sample

Utility

Impulse

Outputs 0.0 except when next is set; in that case it assumes next's value for 1 sample.

impulse = Impulse();
connect(impulse, speaker);

while yield(440) do
  impulse.next = 1;
end
impulse = Impulse()
Constructs an Impulse
impulse.last
The last sample generated by impulse
impulse.next
The value impulse will assume for the next tick

Step

Outputs and sustains whatever value you give it.

step = Step();
connect(step, speaker);

while true do
  step.next = 1;
  yield((1.0 / 440.0) * second)
  step.next = -1;
  yield((1.0 / 440.0) * second)
end
step = Step()
Constructs a Step
step.last
The last sample generated by step
step.next
The value step will assume starting the next tick

Creating a Custom Unit Generator

Although included in the executable, many of ckv's built-in ugens are written in Lua because it's simple and usually good enough. Here is an example:

function Gain(gain)
  return {
    gain = gain or 1.0,
    tick = function(self)
      self.last = UGen.sum_inputs(self);
    end
  }
end

For an in-depth explanation of how object-oriented programming can work in Lua, I recommend the Lua wiki page on OOP. Basically, objects are just tables (similar to PHP arrays in that each is an array and a hashtable in one). g:tick() is actually short-hand for g.tick(g, 1.0), which is short-hand for Gain["tick"](g). Gain returns a table with a tick function that expects a gain object as its argument.

A ugen's tick method is called once for each audio sample. tick need not return anything, but it should set the ugen's last to the ugen's output for this sample.

UGen.sum_inputs(self) returns the sum of the last values for all connected ugens. It's best to call this function only once per tick because the value isn't cached. UGen has no other interesting methods yet.

If you write a ugen you think other people would find useful, consider sharing it in the showcase!

Writing a Unit Generator in C

A lot of the overhead in ugen processing is requesting samples from them all, so a lot of the time creating them using Lua's C API is overkill. However, many unit generators could use the extra speed (like Delay) or access to C APIs (like SndIn).

My recommendation is to check out the audio/ugen/ directory of ckv's source code. There are four steps to adding a unit generator.

  1. Copy an existing unit generator's C file, like audio/ugen/delay.c or audio/ugen/sndin.c. Use its code as a template for your own. A ugen written in C is still a Lua object, but its functions invoke C call-backs. You'll have to follow all of Lua's calling conventions, which may take some getting used to, but are described very well in the Lua manual.
  2. Put the prototype for the C function (open_ugen_*) that creates your ugen's constructor in audio/ugen/ugen.h.
  3. Add your ugen's open_ugen_* function to the array at the top of audio/ugen/ugen.c.
  4. Finally, add the object file for your ugen to the UGEN_OBJECTS list in the Makefile.

Next time you build ckv, your ugen will be included and automatically available!

Language Reference

local variables

Variables don't need to be declared in ckv, so n = 1.0 creates the variable n if it doesn't exist. However, the scope of variables created this way is the entire file.

To declare a local variable, use the local keyboard before its name, as in this example:

function play_sine(freq)
  local s = SinOsc(freq)
  connect(s, speaker)
  yield(second)
  disconnect(s, speaker)
end

fork(play_sine, 400)
fork(play_sine, 600)

If we hadn't used local in that example, the second play_sine thread would have overwritten the global s variable, which is probably not what we want…

global

To share variables between files, put them in global. global is a table that is shared by all running scripts. You can put anything you want in it, like variables, functions, or ugens.

Since it is a table, global.clock = Clock() and global["clock"] = Clock() are equivalent.

c/connect/d/disconnect

connect connects a bunch of ugens together and disconnect disconnects two ugens. c is an alias for connect and d is an alias for disconnect.

d = Delay(3 * seconds)
connect(mic, d, speaker)
disconnect(d, speaker)
connect(a, b, c, …)
Connects a series of ugens together, left to right
disconnect(a, b)
Disconnects a from b

yield/y/sleep

yield causes your thread to sleep for a certain amount of time, allowing other threads to run. y and sleep are aliases for yield.

yield(2 * second)
print("2 seconds later")
sleep(2 * second)
print("2 seconds later")
yield(dur)
Sleeps for the given duration
yield(event)
Waits for an event
yield(dur, clock)
Sleeps for the given duration relative to the provided clock

audio_ffwd

audio_ffwd skips forward in the audio stream, making the virtual machine catch up silently in the background. The function returns immediately. All ugens are ticked for the skipped samples, but their output is discarded.

audio_ffwd(second)
audio_ffwd(dur)
Silences audio output for the given duration

now

while yield(100 * ms) do
  print(now() / second)
end
now()
Returns the current time in samples
now(clock)
Returns the current time in clock beats

Clock

Clock allows you to schedule shreds according to a tempo that can change over time.

clock = Clock(300)

fork(function()
  while yield(1, clock) do
    print(now() / second)
  end
end)

while yield(500 * ms) do
  clock.bpm = 0.9 * clock.bpm
end
clock = Clock(bpm)
Creates a new clock with the given bpm (beats per minute). The default bpm is 120.
clock.bpm
The bpm of a clock

sync

sync yields an amount of time that puts the thread in sync with a certain period. For example, the threads in this example will only ever call print at the start of a beat:

yield(second)
sync(2 * second) -- wake at beginning of next 2-second cycle

clock = Clock()
while yield(0.3, clock) do
  fork(function()
    sync(1, clock)
    print(now(clock))
  end)
end
sync(period)
Sleeps until the start of the next period of length period
sync(period, clock)
Same as sync(period) except the units for period are beats of the given clock, and the sleeping is done against that clock

fork, fork_eval

fork and fork_eval are used for creating new threads.

fork(print, "time to play")
fork(function()
  while yield(second) do
    print("boop!")
  end
end)

fork_eval("print(\"why did I eval this?\")")
fork(f, arg1, arg2, …)
Creates a new thread, calling f with all the following arguments. f can be an anonymous function as in the example above.
fork_eval(str)
Creates a new thread whose code is given in the string str

Event

When you don't know how long a thread should sleep, have it sleep on an event. The thread can be woken whenever you want by calling broadcast on the event object.

fun_time = Event:new();

fork(function()
  while yield(fun_time) do
    print("got the event!");
  end
end)

while true do
  fun_time:broadcast();
  yield(random() * 4 * second);
end
event = Event:new()
Creates an Event that threads can sleep on
event:broadcast()
Wakes up every thread that's sleeping on event

print

Output stuff to the screen using print.

print("begin")
print("the number four is:", 4)
print(1, 2, 3, "do", "re", "mi")
print(s1, s2, s3, …)
Prints the given strings, numbers, or objects, separated by tabs

random/rand, maybe, probably, usually

random()
Generates a random number in [0, 1)
random(i)
Generates a random integer in [1, i]
random(i1, i2)
Generates a random integer in [i1, i2]
rand(…)
rand is an alias for random
maybe()
Might return true
probably()
Will probably return true
usually()
Usually returns true

Home