<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.simtx.net/index.php?action=history&amp;feed=atom&amp;title=Lua_modulation_API</id>
	<title>Lua modulation API - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.simtx.net/index.php?action=history&amp;feed=atom&amp;title=Lua_modulation_API"/>
	<link rel="alternate" type="text/html" href="https://wiki.simtx.net/index.php?title=Lua_modulation_API&amp;action=history"/>
	<updated>2026-08-06T11:38:08Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.43.9</generator>
	<entry>
		<id>https://wiki.simtx.net/index.php?title=Lua_modulation_API&amp;diff=568&amp;oldid=prev</id>
		<title>Clanker: Add and update user documentation for Lua extensions and the catalog</title>
		<link rel="alternate" type="text/html" href="https://wiki.simtx.net/index.php?title=Lua_modulation_API&amp;diff=568&amp;oldid=prev"/>
		<updated>2026-08-06T09:12:25Z</updated>

		<summary type="html">&lt;p&gt;Add and update user documentation for Lua extensions and the catalog&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;A modulation registration receives a context object named &amp;lt;code&amp;gt;ctx&amp;lt;/code&amp;gt;. The same object is passed to the optional &amp;lt;code&amp;gt;start&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;stop&amp;lt;/code&amp;gt; callbacks and to every &amp;lt;code&amp;gt;process&amp;lt;/code&amp;gt; callback. Indices are zero-based.&lt;br /&gt;
&lt;br /&gt;
== Lifecycle ==&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
simtx.modulation {&lt;br /&gt;
  name = &amp;quot;Example&amp;quot;,&lt;br /&gt;
  start = function(ctx)&lt;br /&gt;
    -- Prepare state for this transmission.&lt;br /&gt;
  end,&lt;br /&gt;
  process = function(ctx)&lt;br /&gt;
    -- Produce one complete output block.&lt;br /&gt;
  end,&lt;br /&gt;
  stop = function(ctx)&lt;br /&gt;
    -- Release script state if necessary.&lt;br /&gt;
  end,&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Each transmission receives an isolated copy of the script state. Control values remain live and may change between blocks. Every output sample must be finite.&lt;br /&gt;
&lt;br /&gt;
== Stream information ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Method !! Result&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:n()&amp;lt;/code&amp;gt; || Number of samples in the current block.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:blockSize()&amp;lt;/code&amp;gt; || Alias of &amp;lt;code&amp;gt;n()&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:rate()&amp;lt;/code&amp;gt; || Stream sample rate in hertz.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:sampleRate()&amp;lt;/code&amp;gt; || Alias of &amp;lt;code&amp;gt;rate()&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:time()&amp;lt;/code&amp;gt; || Time in seconds at the start of the block.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:sessionTime()&amp;lt;/code&amp;gt; || Alias of &amp;lt;code&amp;gt;time()&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:samplePosition()&amp;lt;/code&amp;gt; || Absolute sample position at the start of the block.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Reading audio and writing output ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Method !! Effect&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:audio(index)&amp;lt;/code&amp;gt; || Returns an incoming mono audio sample, normally in the range -1 to 1.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:iq(index, i, q)&amp;lt;/code&amp;gt; || Writes one complex output sample.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:map(function(audio, index, time) ... end)&amp;lt;/code&amp;gt; || Maps every input audio sample. The callback returns I and optional Q.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:generate(function(index, time) ... end)&amp;lt;/code&amp;gt; || Generates every sample without using audio. The callback returns I and optional Q.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:passthrough()&amp;lt;/code&amp;gt; || Copies incoming mono audio to the output.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:gain(amount)&amp;lt;/code&amp;gt; || Copies incoming audio to the output with a constant linear gain.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:limit(peak)&amp;lt;/code&amp;gt; || Limits both output components to the symmetric positive peak.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:mixTone(frequencyHz, level)&amp;lt;/code&amp;gt; || Mixes a sine tone with incoming audio and reserves headroom for it.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:mixTone(frequencyHz, level, reserveHeadroom)&amp;lt;/code&amp;gt; || Mixes a sine tone and selects whether the input is reduced by the tone level.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:fill(value)&amp;lt;/code&amp;gt; || Fills an audio block, or the I component of an IQ block, with a constant value.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:fill(i, q)&amp;lt;/code&amp;gt; || Fills the block with a constant complex sample.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:silence()&amp;lt;/code&amp;gt; || Fills the block with zeroes.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;map&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;generate&amp;lt;/code&amp;gt; callbacks receive absolute sample time, not merely time within the current block. This permits phase-continuous generators. Omitting the Q return value sets Q to zero.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
process = function(ctx)&lt;br /&gt;
  local gain = ctx:control(&amp;quot;gain&amp;quot;)&lt;br /&gt;
  ctx:map(function(audio)&lt;br /&gt;
    return audio * gain&lt;br /&gt;
  end)&lt;br /&gt;
  ctx:limit(1)&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Block operations such as &amp;lt;code&amp;gt;gain&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;mixTone&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;fill&amp;lt;/code&amp;gt; are preferable when they express the required operation because they avoid a Lua callback for every sample.&lt;br /&gt;
&lt;br /&gt;
== Controls and inputs ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Method !! Result&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:control(id)&amp;lt;/code&amp;gt; || Current value of one control, or &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; for an unknown ID.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:controls()&amp;lt;/code&amp;gt; || Snapshot table containing all current control values.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:input(id)&amp;lt;/code&amp;gt; || Decoded image, video, or file input, or &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:inputs()&amp;lt;/code&amp;gt; || Snapshot table containing all supplied non-audio inputs.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:hasInput(id)&amp;lt;/code&amp;gt; || Whether a non-audio input was supplied.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;ctx:finish()&amp;lt;/code&amp;gt; || Requests a clean end after the current block.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Audio is accessed through &amp;lt;code&amp;gt;audio&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;map&amp;lt;/code&amp;gt;, and the block operations rather than through &amp;lt;code&amp;gt;input&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Image objects ==&lt;br /&gt;
Image coordinates are zero-based. Channel and luminance values are normalized from 0 to 1.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Method !! Result&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;image:name()&amp;lt;/code&amp;gt; || Original filename.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;image:width()&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;image:height()&amp;lt;/code&amp;gt; || Dimensions in pixels.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;image:red(x,y)&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;green&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;blue&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;alpha&amp;lt;/code&amp;gt; || One normalized channel.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;image:luma(x,y)&amp;lt;/code&amp;gt; || Rec. 709 luminance.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;image:pixel(x,y)&amp;lt;/code&amp;gt; || Table with &amp;lt;code&amp;gt;r&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;g&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;b&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;a&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;luma&amp;lt;/code&amp;gt; fields.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;image:argb(x,y)&amp;lt;/code&amp;gt; || Packed ARGB integer.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;image:resize(width,height)&amp;lt;/code&amp;gt; || High-quality resized image object.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
An image side may contain at most 8,192 pixels, and an image may contain at most 16,777,216 pixels.&lt;br /&gt;
&lt;br /&gt;
== Video objects ==&lt;br /&gt;
Video frame indices are zero-based.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Method !! Result&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;video:name()&amp;lt;/code&amp;gt; || Original filename.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;video:width()&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;video:height()&amp;lt;/code&amp;gt; || Frame dimensions.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;video:frames()&amp;lt;/code&amp;gt; || Number of frames.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;video:duration()&amp;lt;/code&amp;gt; || Duration in seconds.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;video:frameRate()&amp;lt;/code&amp;gt; || Average frames per second.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;video:frame(index)&amp;lt;/code&amp;gt; || Decoded frame as an image object.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;video:frameAt(seconds)&amp;lt;/code&amp;gt; || Frame at the requested time as an image object. Times outside the duration are clamped.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Video decoding supports MP4 and MOV-family files accepted by the picker. A script should normally obtain one frame per logical video frame and reuse it while producing the corresponding samples.&lt;br /&gt;
&lt;br /&gt;
== File objects ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Method !! Result&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;file:name()&amp;lt;/code&amp;gt; || Original filename.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;file:size()&amp;lt;/code&amp;gt; || Size in bytes.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;file:byteAt(index)&amp;lt;/code&amp;gt; || Unsigned byte from 0 to 255 at a zero-based index.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;file:text()&amp;lt;/code&amp;gt; || Entire payload decoded as UTF-8 text.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
A generic file input may contain at most 64 MiB.&lt;br /&gt;
&lt;br /&gt;
[[Category:Lua extensions]]&lt;/div&gt;</summary>
		<author><name>Clanker</name></author>
	</entry>
</feed>