Skip to content

Macros

Macros provide compile-time code generation via #param# text substitution. They expand at compile time, replacing parameter names with the caller's literal arguments.

Syntax

macro name(#param1#, #param2#, ...)
    // macro body with #param# substitution
end macro

Basic Macros

macro util.swap(#a#, #b#)
    util.swap := #a#
    #a# := #b#
    #b# := util.swap
end macro

Calling util.swap(x, y) expands to:

util.swap := x
x := y
y := util.swap

Parameter Substitution

Parameters are purely textual -- #param# is replaced by whatever text was passed. Parameters concatenate with surrounding text to form identifiers:

macro type._Property(#type#, #field#)
    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 property
end macro

Calling type._Property(Voice, note) generates a Voice.note property with getter and setter accessing Voice.field.note.

Parameters in strings are substituted too:

macro cluster.Validate(#cluster#, #member#)
    if not (#cluster#.validate.#member#(#cluster#[self].#member#))
        message('ArgFailure (#cluster#): #cluster#[self].#member# invalid: ' & #cluster#[self].#member#)
        self_invalid := TRUE
    end if
end macro

Nested Macro Calls

Macros can call other macros:

macro ivls.wait_ms(#ms#)
    ivls.wait_us((#ms#) * 1000)
end macro

macro ivls.wait_us(#micros#)
    ivls.wait_us.unsafe(#micros#)
    if not check_ref(Voice, self)
        NodeEnv[nenv].info.path_cancelled := TRUE
        __RUN_CB__(PathCancellation)
    end if
end macro

macro ivls.wait_us.unsafe(#micros#)
    if #micros# > 0
        tcm.wait(#micros#)
    end if
end macro

ivls.wait_ms(100) -> ivls.wait_us(100000) -> ivls.wait_us.unsafe(100000) + safety checks.


Macros Generating Code Constructs

Generating Functions

macro type.Functions(#type#)
    function #type#.new() -> result
        call #type#.new.internal()
        declare global #type#.new.result
        result := #type#.new.result
    end function

    function #type#.delete(ref)
        declare global #type#.delete.ref := ref
        call #type#.delete.internal()
    end function

    function #type#.copy(src) -> result
        declare global #type#.copy.src := src
        call #type#.copy.internal()
        declare global #type#.copy.result
        result := #type#.copy.result
    end function
end macro

type.Functions(Voice) generates Voice.new(), Voice.delete(), Voice.copy(), etc.

Generating Properties

macro kontakt._add_mod_par(#prop#, engine_par)
    property group.mods.#prop#
        function get(group, mods) -> result
            result := get_engine_par(engine_par, group, get_mod_idx(group, mods), -1)
        end function
        function set(group, mods, value)
            set_engine_par(engine_par, value, group, get_mod_idx(group, mods), -1)
        end function
    end property
end macro

Constructor/Destructor Patterns

macro Voice.Constructor(#ref#)
    declare ref := #ref#
    __RUN_CB__(VoiceConstructor)
end macro

macro Voice.Destructor(#ref#)
    declare ref := #ref#
    __RUN_CB__(VoiceDestructor)
end macro

macro Voice.CopyConstructor(#ref#)
    declare ref := #ref#
    __RUN_CB__(VoiceCopyConstructor)
    ivls._refurbish_voice(ref)
end macro

Type System Macros

macro type.Create(#type#)
    type.CreateCustomPool(#type#, 524288)
end macro

macro type.CreateCustomPool(#type#, #pool#)
    const #type#.field
        literate_post_macro(#l#) on #type#.MEMBERS
    end const

    declare const #type#.BLOCK := #type#.field.SIZE
    declare const #type#.POOL_SIZE := #pool# - (#pool# mod #type#.BLOCK)
    declare #type#.pool[#type#.POOL_SIZE] := (-1)
    declare #type#.is_alloc[#type#.POOL_SIZE / #type#.BLOCK]
    declare #type#.count := 0

    literate_post_macro(type._Property(#type#, #l#)) on #type#.MEMBERS

    #type#.def()
end macro

Utility Macros

macro util.array.fill(#arr#, #value#)
    for util.fill_i := 0 to num_elements(#arr#) - 1
        #arr#[util.fill_i] := #value#
    end for
end macro

macro Voice.WriteInitList(#array#)
    for Voice.i := 0 to num_elements(#array#) - 1
        Voice.INIT[Voice.write_idx] := #array#[Voice.i]
        inc(Voice.write_idx)
    end for
end macro

macro util.log(#text#)
    USE_CODE_IF(DEBUG)
        message(#text#)
    END_USE_CODE
end macro

Scope and Expansion

  • Macros expand at compile time, not runtime
  • Variables declared inside a macro follow normal scoping after expansion
  • Use dotted names for macro namespacing: util.array.fill, cluster.Validate, ivls.play
  • Use unique prefixed temp variables (util.fill_i) to avoid conflicts across expansions
  • Parameters are evaluated at each occurrence in the expanded code

See Also