Functions¶
IVLS-KSP supports two function types: standard functions (inlined) and task functions (coroutines with a real call stack).
Standard Function Syntax¶
function name(param1, param2, ...)
// function body
end functionfunction ivls._fire_voice(vo)
ivls._refurbish_voice(vo)
Voice[vo].input := ivls.input_type.VOICE_ON
Voice[vo].note_on_time := ENGINE_UPTIME
inc(Voice[vo].stage)
ivls._process_voice(vo)
end functionInlining Semantics¶
Standard functions are inlined via textual substitution. There is no call stack and no recursion. At compile time, each function call is replaced with the function body, and parameter names are substituted with the caller's arguments.
Because of inlining, modifying a parameter inside a function directly modifies the caller's variable -- this is a natural consequence of textual substitution, not a separate "pass by reference" mechanism.
function math.set_nth_bit(B, N, val)
B := BOOL.CHOOSE(val, ...
sh_left(1, N) .or. B, ...
(math.ALLMASK - sh_left(1, N)) .and. B ...
)
end functionHere, assigning to B modifies the caller's variable because B is textually replaced with whatever was passed in.
function math.rand_seeded(min, max, seed) -> return
seed := 8088405 * seed + 1
return := (sh_right(seed, 8) .and. 0xFFFFFF) mod (max - min + 1) + min
end functionWhen called as math.rand_seeded(0, 100, my_seed), the seed parameter is the caller's my_seed variable after inlining, so my_seed is updated.
Return Values¶
Functions return values using the -> return syntax. The return variable is assignable within the function body:
function math.amp_to_db(a) -> return
return := (20.0 * math.log10(a))
end functionNamed Return Variables¶
Return variables can have descriptive names:
function math.equal_power(in) -> ratio
ratio := sqrt(sin(0.5 * NI_MATH_PI * in))
end functionfunction ivls.RoundRobins.query_rr(vo) -> rr_pos
declare rr_idx := ivls.RoundRobins.query_rr_idx(vo)
declare max := ivls.RoundRobins.hooks.get_max_rr(vo)
if not in_range(rr_idx, 0, rr.MAX_IDX)
message("RoundRobins.query_rr(): Invalid rr_idx found: " & rr_idx)
rr_pos := -1
else if max = 0
rr_pos := 0
else
rr_pos := rr.main_seed[max - 1, rr.counter[rr_idx]]
end if
end functionLocal Variables¶
Local variables are declared within a function using declare:
function ivls._refurbish_voice(v)
declare i
for i := 0 to num_elements(Voice.RESET_ON_NEW) - 1
Voice[v].access[Voice.RESET_ON_NEW[i]] := Voice.INIT[Voice.RESET_ON_NEW[i]]
end for
end functionFloat locals use the ~ prefix:
function math.get_timestretched_offset(root_note, result_note, ideal_transient, extra_offset) -> result
declare ~stretch_factor := pow(2.0, float(root_note - result_note) / 12.0)
declare ~perceptual_transient := ideal_transient * ~stretch_factor
declare ~converted_offset := extra_offset / ~stretch_factor
declare ~corrective_offset := (perceptual_transient - ideal_transient) / ~stretch_factor
result := corrective_offset + converted_offset
end functionTask Functions (Coroutines)¶
Task functions have a real TCM call stack, support var/out parameter modifiers, and can recurse. They can yield execution using wait operations.
taskfunc name(params)
// function body with potential wait operations
end taskfuncThe var Keyword¶
Task functions use var to explicitly mark by-reference parameters:
taskfunc ivls.send_voice(var self, var self_invalid, vo_input, nenv)
declare vo_to_send
declare vo_as_parent
NodeEnv[nenv].sentVoice := -1
if vo_input = self
message('Calling ivls.play() on \'self\' not supported in IVLS!')
self_invalid := TRUE
end if
if not check_ref(Voice, self)
vo_as_parent := ivls.omni_voice
vo_to_send := vo_input
else
vo_as_parent := self
if vo_input = self
vo_to_send := ivls.new_voice(self)
else
vo_to_send := vo_input
end if
end if
if not check_ref(Voice, vo_to_send)
if vo_to_send = self
message('Node ' & ivls.node_name(nenv) & ' tried to play passive input after release!')
else
message('Node ' & ivls.node_name(nenv) & ' tried to play invalid Voice!')
cluster.Dump(Voice, self)
end if
else if self_invalid = TRUE
message('IVLS has been halted at node ' & ivls.node_name(nenv))
cluster.Dump(Voice, self)
Voice.delete(vo_to_send)
else if NodeEnv[nenv].info.path_cancelled = TRUE and not (Voice[vo_to_send].duration > 0)
if vo_input = self
Voice.delete(vo_to_send)
end if
else
if Voice[vo_to_send].stage < ivls.flow_lengths[Voice[vo_to_send].flow] - 1
ivls._fire_new_child(vo_to_send, vo_as_parent)
NodeEnv[nenv].sentVoice := vo_to_send
else
Voice.delete(vo_to_send)
end if
end if
end taskfuncvar selfandvar self_invalidare passed by reference (changes reflect in the caller)vo_inputandnenvare passed by value
Taskfunc with Waiting¶
taskfunc #name#.process_label_revert(#name#.DIMS)
#name#.par_label_in_use[#name#.DIMS] := TRUE
tcm.wait(UI.PARAM_DISP_DELAY_MS * 1000)
if #name#.par_label_in_use[#name#.DIMS] = TRUE
#name#.par_ctrl_label[#name#.DIMS] -> text := #name#.par_label_original_text[#name#.DIMS]
#name#.par_label_in_use[#name#.DIMS] := FALSE
end if
end taskfuncCalling Conventions¶
// Basic calls
ivls._fire_voice(vo)
math.rand_reseed()
// Capturing return values
declare result := math.clamp(value, 0, 100)
declare db := math.amp_to_db(0.5)
// Return values in expressions
Voice[vo].rr := ivls.RoundRobins.query_rr(vo)
return := math.min(math.max(num, min), max)Function Namespacing¶
IVLS-KSP uses dotted names for logical namespaces. At compilation, dots become double underscores:
math.amp_to_db(a) // Math library
ivls.add_child_to_parent() // IVLS voice operations
ivls.RoundRobins.query_rr() // Round-robin functionsType-associated functions follow the pattern Type.function_name:
function Voice.new() -> result
function Voice.delete(ref)
function Voice.copy(src) -> resultProperties with Functions¶
Properties use functions for getters and setters, enabling the bracket accessor syntax:
property #type#.#field#
function get(pointer) -> result
result := #type#.pool[pointer * #type#.BLOCK + #type#.field.#field#]
end function
function set(pointer, value)
#type#.pool[pointer * #type#.BLOCK + #type#.field.#field#] := value
end function
end propertyUsage: Voice[ref].note calls get(ref), Voice[ref].note := 60 calls set(ref, 60).
Hook Functions¶
Hook functions provide overridable extension points:
// Default implementation
function ivls.RoundRobins.hooks.get_max_rr(vo) -> rr_max
rr_max := 0
end function
// Product code override
function ivls.RoundRobins.hooks.get_max_rr(vo) -> max
max := get_sample_rr_count(Voice[vo].note, Voice[vo].dyn_layer)
end functionSee Also¶
- Macros - Compile-time code generation
- Control Flow - Loops and conditional statements
- Callbacks - Event-driven function execution
- Types - Type-associated functions and properties