Specification of the JSON script schema for the not-things TimeSeq module.
Since the TimeSeq JSON schema uses a nested object structure, following hierarchical object overview can be used as TOC for navigating around:
eq
, ne
, lt
, lte
, gt
, gte
, and
, or
trigger
- Fire an internal triggerAs new features are added, the version
of the script is updated. The script version page gives an overview of these changes.
To facilitate editing of the JSON script in JSON Schema aware editors, a schema can be
Next to the JSON objects that are defined in this document, following property type definitions are used throughout the JSON format specification:
true
or false
[]
). E.g. a list of three strings is written as: [ "string one", "string two", "string three" ]
The root item of the TimeSeq JSON script.
Sequencing is done by adding one or more timeline objects to the timelines
list property. Each processing cycle of the script will go through the timeline objects in the order that they appear in this list (in case execution order is important for the processing of the sequences, e.g. when setting and reading variables).
The global-actions
property allows specific actions to be executed when a script is loaded or reset (e.g. setting the number of channels on a polyphonic output). Only actions that have a timing
set to start
can be added to this list.
If there is a need to generate internal TimeSeq triggers based on external trigger sources, the input-triggers
property allows input ports to be set up for receiving external trigger signals.
In the component-pool
, TimeSeq objects (segments, inputs, outputs, values, …) can be defined that can then be referenced from elsewhere in the script. This allows a single object definition to be re-used in multiple parts of the script, and can allow better structuring of complex scripts through the usage of clear/descriptive IDs. See the referencing section of the main TimeSeq script documentation file for more details.
property | required | type | description |
---|---|---|---|
type |
yes | string | Must be set to not-things_timeseq_script |
version |
yes | string | Identifies which version of the TimeSeq JSON script format is used. Currently versions 1.0.0 and 1.1.0 are supported (see this page for features included in each version). |
$schema |
no | uri string | Allows JSON schema validation to be performed by schema-aware JSON editors. See the script version page for the schema URIs that can be used. The value given to this property will not influence TimeSeq parsing or processing itself. |
timelines |
no | timeline list | A list of timelines that will drive the sequencer. |
global-actions |
no | action list | A list of actions that will be executed when the script loaded or is reset. Only actions which have their timing set to start are allowed in this list. |
input-triggers |
no | input-trigger list | A list of input trigger definitions, allowing gate/trigger signals on input ports to be translated into internal TimeSeq triggers. |
component-pool |
no | component-pool | A pool of reusable TimeSeq object definitions that can be referenced from elsewhere in the TimeSeq script. |
{
"type": "not-things_timeseq_script",
"version": "1.0.0",
"timelines": [
{ ... },
{ ... }
],
"global-actions": [
{ ... },
{ ... }
],
"input-triggers": [
{ ... },
{ ... }
],
"component-pool": {
...
}
}
A timeline is a container for the sequencing definitions of a script. It groups together one or more lanes.
An optional time-scale property controls the timing calculations that will be performed for all lanes (and thus the duration of their segments).
If there are looping lanes present in this timeline, the loop-lock
property will define when the lanes loop: if loop-lock
is enabled, any lane that reaches the end of its processing will not restart until all other lanes (looping or non-looping) have finished processing. Once all lanes have finished, those that should loop will loop together. If loop-lock
is not enabled, any lane that finishes processing and is set to loop will do so immediately.
When running the script, each processing cycle will run through the lanes in the order that they appear in the lanes
list.
property | required | type | description |
---|---|---|---|
time-scale |
no | time-scale | The time scale that should be used when calculating durations of segments in this timeline. |
loop-lock |
no | boolean | If true , lanes will only loop once all other lanes have completed. If false , lanes loop immediately when finished. Defaults to false if not set. |
lanes |
yes | lane list | The lanes that contain the segment sequences for this timeline. |
{
"time-scale": {
"bpm": 120
},
"loop-lock": true,
"lanes": [
{ "segments": [ { "ref": "segment-1" } ] },
{ "segments": [ { "ref": "segment-2" } ] }
]
}
The time-scale object defines how certain timing calculations should be performed for a timeline. Although the properties of a time-scale are all optional, if a time-scale is added to a timeline then at least one of sample-rate
or bpm
must be present.
Using samples is the most fine-grained scale to specify timing within TimeSeq. However, since VCV Rack allows the active sample rate to be changed, the exact sample rate at which the script will be running may not be known in advance. The sample-rate
property allows you to specify that any segments in this timeline that use samples
for their duration
have been configured assuming that they are running at the specified sample rate. When parsing the script, TimeSeq will remap the provided duration of these segments so that they will last as long as they would have under the specified sample-rate E.g. if sample-rate
is set to 48000, but VCV Rack is running a 96000 (96Khz) sample rate, a segment with a 250 samples duration will instead last 500 samples (since there are double the amount of samples per second at 96Khz when compared to the expected 48Khz).
Note that sample rate recalculation can not make a segment shorter then one sample. If the sample duration recalculation of a segment goes below one sample, it will instead last one sample.
In order to facilitate the definition of musical sequences, the time-scale allows the Beats per Minute and Beats per Bar to be defined for all segments in this timeline through the bpm
and bpb
properties. When a bpm
value is set, all segments in this timeline can specify their duration using the beats
property. If the number of beats in a bar has also been specified through the bpb
property, segments can also specify a duration in bars
.
A bpb
value can only be set if there is also a bpm
value set.
property | required | type | description |
---|---|---|---|
sample-rate |
no | unsigned number | The sample rate in which the samples duration of all segments in the timeline are expressed. |
bpm |
no | unsigned number | The number of Beats per Minute to use for all segments in the timeline that specify their duration using beats . |
bpb |
no | unsigned number | How many beats go into one bar for all segments in the timeline that use a bars duration. bpb can only be set if bpm is also set. |
"time-scale": {
"sample-rate": 48000,
"bpm": 120,
"bpb": 4
}
Lanes provide the core sequencing functionality of TimeSeq. A lane will activate the segments
that are assigned to it in the order that they appear in the list. Only one segment can be active at a time within a lane. When a segment completes, the lane will move on to the next segment in the list.
Several properties control how a lane executes:
auto-start
defines if the lane should automatically be started when the script is started.loop
defines if the lane should keep looping the segments
: if enabled, the first segment will be activated again when the last segment of the list completes.repeat
defines how many times the segments
in the lane should be repeated. A value of 0 or 1 mean that the list of segments
will only be executed once. A value of 2 results in running through the list twice, 3 results in three iterations, etc. If loop
is enabled for the lane, the segments
will be repeated indefinitely, so the repeat
property will have no impact anymore in that case.The running state of a lane can be controlled using triggers:
start-trigger
property fires and the lane is not currently running, that lane will be started. Any previous progress of the lane will be reset, and it will start from its first segment. If the lane was already running when the trigger was received, the trigger will have no impact on the lane status or position.restart-trigger
property fires, the lane will be restarted from its first segment. If the lane was paused or hadn’t started yet, it will start running from the first segment. If the lane was already running, its position will be reset to that of the first segment.stop-trigger
property fires and the lane is currently running, that lane will stop running. If it is not running, the trigger will have no impact.property | required | type | description |
---|---|---|---|
segments |
yes | segment list | The sequence of segments that will be executed for this lane |
auto-start |
no | boolean | If set to true , the lane will start automatically when the script is loaded. If set to false the lane will remain stopped when the script is loaded. Defaults to true |
loop |
no | boolean | If set to true , the lane will restart from its first segment once its last segment has completed. Otherwise the lane will stop once its last segment completes. Defaults to false |
repeat |
no | unsigned number | Specifies how many times the segments in the lane should be repeated before stopping the lane. Both values 0 and 1 mean that the segments are executed once. This property has no impact if loop is set to true . Defaults to 0 |
start-trigger |
no | string | The id of the internal trigger that will cause this lane to start running from its first segment. A start trigger on an already running lane has no impact on the state of that lane. Defaults to empty. |
restart-trigger |
no | string | The id of the internal trigger that will cause this lane to restart. A restart trigger on an inactive lane will cause it to start running. A restart trigger on a running lane will cause it to restart from the first segment. Defaults to empty. |
stop-trigger |
no | string | The id of the internal trigger that will cause this lane to stop running. A stop trigger on an an inactive lane has no impact on the state of that lane. Defaults to empty. |
disable-ui |
no | boolean | If set to true , the L LED on the TimeSeq panel will light up when this lane loops. If set to false , a loop of this lane will not cause the L LED on the TimeSeq panel to light up. Defaults to true . |
{
"auto-start": true,
"loop": true,
"restart-trigger": "restart-chord-lane",
"segments": [
{ "ref": "segment-1" },
{ "ref": "segment-2" }
]
}
Identifies that an input port that will be monitored for trigger input signals.
An input will be considered to have ‘triggered’ if it went from low voltage (0V) to high voltage (more then 1V). After being triggered, the input signal must first return back to low voltage (0V) before it can be triggered again.
When an input has been triggered, the internal trigger (identified by the id
property) will be set, which can then influence the running state of a Lane if it uses that trigger id as start-trigger
, restart-trigger
or stop-trigger
.
property | required | type | description |
---|---|---|---|
id |
yes | string | The id of the trigger that will be set when an input trigger is detected. |
input |
yes | input | The input that will be monitored for input triggers. |
{
"id": "start-chord-sequence",
"input": {
"index": 5,
"channel": 2
}
}
A pool of reusable script components. Objects defined in this pool aren’t added into the script directly from here, but can instead be referenced throughout the script using the ref mechanism.
All objects defined in this pool must have an additional id
property, since this will be used to reference them from within the script. It is allowed to use the same id
value for different types of objects (e.g. for a segment and a value), but within one type of object, the id
must be unique.
See referencing in the script overview page for more information about using re-usable components.
property | required | type | since | description |
---|---|---|---|---|
segment-blocks |
no | segment-block list | A list of reusable segment-block objects. | |
segments |
no | segment list | A list of reusable segment objects. | |
inputs |
no | input list | A list of reusable input objects. | |
outputs |
no | output list | A list of reusable output objects. | |
calcs |
no | calc list | A list of reusable calc objects. | |
values |
no | value list | A list of reusable value objects. | |
actions |
no | action list | A list of reusable action objects. | |
ifs |
no | if list | A list of reusable if objects. | |
tunings |
no | tuning list | 1.1.0 | A list of tuning objects that can be used in quantize calcs |
{
"component-pool": {
"inputs": [
{
"id": "status-input",
"index": 3,
"channel": 5
}
],
"values": [
{ "id": "one-and-a-half", "voltage": 1.5 },
{ "id": "full", "voltage": 10 }
]
}
}
Segments provide the core timing functionality of TimeSeq. Through its duration
property, a segment specifies how long it should last. And since lanes execute their segments in order one by one, this allows sequences with more complex timings to be created.
The actual output of a segment is determined by its list of actions
. Different types of actions exist, with their timing
specifying when they should be executed (See action and its sub-types for more details).
Depending on the script, the order that the actions are executed in can be of importance (e.g. when writing and subsequently reading variables). This execution order follows a predefined logic. A segment first groups the actions in three sets according to their timing: start actions, ongoing actions (with a glide
or gate
timing) and end actions. In each processing cycle, the processing order of these actions then becomes:
The segment-block
property allows for a special version of a segment: if present, the segment-block
property must contain the ID of a segment-block in the segment-blocks
section of the component-pool. The segments of that segment-block will then take the place of the original segment, as if they were added inline. The segment-block
property can not be combined with the duration
property within the same segment instance: either it is a stand-alone segment with a duration
and actions
, or it is a link to a segment-block
.
The actions
property can still be used together with the segment-block
property, but in that case, it can only contain actions with a start
or the end
timing
. In this case, the start
actions will be executed before the first segment of the segment-block starts. The end
actions will be executed when the last segment of the segment-block has completed. If the segment-block has a repeat
value configured, the actions will not be executed each time the segment-block repeats. They will only execute at the start of the first repeat, and after the last repeat has completed.
property | required | type | description |
---|---|---|---|
duration |
yes | duration | Defines how long this segment will take to complete. |
actions |
no | action list | The actions that will be executed as part of this segment. See the description above for details about the timings of actions. |
disable-ui |
no | boolean | If set to true , the S LED on the TimeSeq panel will light up when this segment starts. If set to false , a start of this segment will not cause the S LED on the TimeSeq panel to light up. Defaults to true . |
segment-block |
no | string | The ID of a segment-block in the component-pool that will take the place of this segment. Can not be combined with the duration , and disable-ui properties. |
{
"duration": { "beats": 2 },
"actions": [
{ "timing": "start", "set-variable": { "name": "next-note", "value": { "voltage": 1.333 } } },
{ "timing": "end", "set-output": { "output": { "index": 1 }, "value": { "voltage": 1.333 } } }
]
}
{
"segment-block": "three-notes-and-a-beat"
}
The duration section defines how long a segment will last. TimeSeq allows the duration to be expressed in several types of units: samples, milliseconds, beats & bars or Hertz. Only one of these units can be used at a time for a segment duration, except for beats
and bars
, which can be used together.
Since TimeSeq works based on VCV Rack samples, all of these types of units will be converted into samples once the script is loaded. The final converted duration of a segment can not be shorter then one sample. If any of the unit conversions result in a duration that is shorter then one sample, the segment will last one sample instead. TimeSeq does allow fractional durations above that however (e.g. a segment can end up lasting 1.2 samples), and TimeSeq will keep track of these fractions to try and avoid drifts over time between different lanes.
Samples are the smallest time division in VCV Rack and thus in TimeSeq. The duration of a sample depends on the current sample rate of VCV Rack. E.g. if the current sample rate is 48Khz, there will be 48000 samples per second, and each sample will thus last 1/48000th of a second.
Since the sample rate of VCV Rack (and thus the length of a sample) may not be known in advance, TimeSeq allows sample durations to be “re-mapped”: if the time-scale of the timeline in which the segment appears has a sample-rate
property, that sample rate will be used instead to calculate the duration of the segment.
E.g. if the timeline sample-rate
is set to 48000, but VCV Rack is running at 96Khz, all segments in that timeline that express their duration with a sample
value will have this value multiplied by 2 (96000 / 48000 = 2). The end result will be that the segment will have the same duration (in absolute time) independent of the actual VCV Rack sample rate.
Specifies the duration of a segment in milliseconds. Decimal values are allowed.
If the time-scale of the timeline in which the segment appears has a bpm
configured, the duration of a segment can be expressed in the number of beats
relative to those Beats per Minute. Decimal values are allowed so that partials of beats (e.g. 8ths, 16ths, …) can be expressed.
If there is also a bpb
configured in the time-scale, an additional bars
property is available relative to those Beats per Bar. bars
can not be decimal, and a bars
property can not be used if no beats
property is present, though the beats
property can be set to 0
to specify that the segment last exactly the length of a (number of) bar(s).
A Hertz duration indicates how often the duration of the segment should fit within one second. E.g. if hz
is set to 5, the sample will last 1/5th of a second and thus 200 milliseconds. This value can be a decimal.
property | required | type | description |
---|---|---|---|
samples |
no | unsigned number | The number of samples that the segment will last. Relative to the sample-rate of the time-scale of the current timeline, or to the active VCV Rack sample rate if none was specified on the timeline |
millis |
no | unsigned float | Number of milliseconds that the segment will last |
beats |
no | unsigned float | Number of beats that the segment will last, Relative to the bpm of the time-scale of the current timeline |
bars |
no | unsigned number | Number of bars that the segment will last, Relative to the bpb of the time-scale of the current timeline. Can not be used without beats |
hz |
no | unsigned float | Expresses the duration of the segment in Hertz, or fractions of a second |
{
"millis": 1.25
}
{
"hz": 174.61
}
{
"beats": 0.25
}
{
"beats": 0,
"bars": 2
}
Instead of using a fixed numeric value for the duration (e.g. "beats": 0.25
), it is also possible to use a value for the samples
, millis
, beats
or hz
properties. This allows the duration of the segment to vary based on the evaluated voltage of the variable. Each time a segment starts, if the duration is set to a value object instead of a fixed numeric, that value will be evaluated and the result will be used as duration segment. The next time that segment is executed, the value will be re-evaluated and may result in a different duration.
Since the actions of a segment can have an impact on the resulting voltage of a value, a fixed order of operations is followed:
start
timing will be executedglide
and gate
actions are executedend
actions are executedThis means that start
actions can be used in a segment to influence the duration of that segment. However, once this length has been determined, the remaining actions (glide
, gate
or end
) will not influence it anymore, even if they modify properties that the original duration calculation was based on.
Just like constant-length durations, a variable-length durations can not be made shorter then one sample. If the duration evaluates to a length shorter then one sample, it will be made to last one sample instead. Fractional sample durations (longer then 1 sample) are supported however, resulting in drift compensation by TimeSeq over time.
When using a value-based beats
duration, it is not possible to also specify a bars
property.
Variable-length durations were introduced in TimeSeq script version 1.1.0.
The duration is expressed in beats
, its length is determined by the voltage of input 2:
{
"beats": { "input": 2 }
}
The duration is expressed in millis
, which is set to the current value of the my-segment-duration
variable that gets truncated (i.e. the decimal part removed, only the whole value is used)
{
"millis": {
"variable": "my-segment-duration",
"calc": { "trunc": true }
}
}
A segment-block allows multiple segments to be grouped together so that they can easily be added in other places within the script. The segments in a segment-block can be full inline segments, references to re-usable segments (using the ref
property) or references to other segment-block instances.
The segments
in the block will be executed in the order that they appear in the list. The repeat
property allows the full list to be repeated a number of times.
segments-blocks
are defined in the component-pool, and used by referencing them by id
in the segment-block
property of a segment.
property | required | type | description |
---|---|---|---|
segments |
yes | segment list | The list of segments in this segment-block |
repeat |
no | unsigned number | The amount of times that the segments list should be repeated. |
{
"id": "segment-block-1",
"segments": [
{ "ref": "segment-1" },
{ "ref": "segment-2" },
{ "segment-block": "segment-block-2" },
{ "ref": "segment-3" }
],
"repeat": 3
}
Actions provide the functional core of a TimeSeq script. When executed in a segment, actions can change output voltages, change output polyphony, set variables or fire internal triggers.
An optional if
property is available on actions that allows their execution to be made optional. If the condition specified by the if is met, the action will be executed. If not, the action will be skipped.
The timing
property of an action identifies when the action will be executed by the segment that contains it. Based on the timing
property, three major types of actions can be identified:
start
timing), or when the segment ends (end
timing),glide
timing).gate
timing)The order in which the actions of a segment are executed is described in the segment section.
Actions that have a start
or an end
timing
will be executed once at the appropriate time in the segment (i.e. when it starts or ends). If an if
property is present on the action, the condition of that if will be evaluated at the time that the segment tries to execute the action. If the condition of the if is not met, the action will not be executed.
start
and end
actions can perform a number of operations, each with their own appropriate properties:
Each action must contain exactly one of these operation. If multiple operations need to be performed, separate actions will have to be created for each of them.
Except for the trigger
action, all the action operations have their own action property that will contain a sub-object with the appropriate parameters for that operation. Since trigger
action only needs to identify the ID of the trigger that has to be fired, a plain string that identifies that trigger ID will be sufficient.
property | required | type | description |
---|---|---|---|
timing |
no | string | Identifies the timing when this action will be executed. Can be either start or end . Defaults to start . |
set-value |
no | set-value | Sets a voltage on an output port. |
set-polyphony |
no | set-polyphony | Sets the polyphony of an output port. |
set-label |
no | set-label | Sets the tooltip label of an output port. |
set-variable |
no | set-variable | Sets a variable. |
assert |
no | assert | Performs an assert. |
trigger |
no | string | Fires an internal trigger with the specified id. |
if |
no | if | A condition that must be met in order for the action to be executed. |
{
"timing": "start",
"set-value": {
"value": { "voltage": 6.9 },
"output": { "index": 4, "channel": 2 }
}
}
{
"timing": "end",
"set-variable": {
"name": "my-first-variable",
"value": { "voltage": 1.23 }
}
}
{
"timing": "end",
"trigger": "trigger-next-sequence"
}
Actions with a glide
timing gradually move from one value to another over the duration of a segment. The start
value of the action will define at which voltage the glide starts, and the end
value identifies the voltage the action will reach at the end of the segment.
Using the ease-factor
property, it is possible to influence the rate at which the glide moves from the start
value to the end
value. A positive ease-factor
will cause the change to start slow and speed up towards the end, while a negative one will cause it to start changing quickly and ease out towards the end. If no ease-factor
is specified (or it is set to zero), the glide will be executed in a linear fashion.
The calculation of the easing factor supports two algorithms: using sigmoid function (sig
) or based on power calculations (pow
). The arc resulting from these algorithms differs slightly. By default, the sig
algorithm will be used since it is less CPU intensive.
Just like with other actions, a glide action can be made conditional by including an if
property so that the action will only be executed if the if condition is met.
The exact values used for the start
, end
and if
properties will be calculated when the segment that contains the glide action is started. They will not be re-evaluated while the segment is running, so any change that could influence these value calculations that happens while the segment is running will not be taken into account anymore. For example, if the action was determined to be disabled due to its if
condition when the segment started, the action will not become active afterwards while the segment is running parameter values for that if
condition change in between. If the segment is started again at a later time however (e.g. due to a looping lane), all values for the action will be re-evaluated when the segment restarts.
A glide action has two possible targets to send its generated voltages to: either change an output voltage or set a variable that can be used in other areas of the script. Only one of these targets can be used per glide action. A glide action that is executed will update the voltage of its target (the output or the variable) in each processing cycle (i.e. at the active sample rate of VCV Rack).
property | required | type | description |
---|---|---|---|
start-value |
yes | value | The value that the action should start the glide from. |
end-value |
yes | value | The value that the action should glide towards. |
ease-factor |
no | float | Controls the rate at which the action will move from the start value to the end value. Must be between -5 and 5. Defaults to 0 |
ease-algorithm |
no | string | The algorithm to use for easing calculations. Can be either sig or pow . Defaults to sig |
output |
no | output | The output port to which the calculated value should be sent |
variable |
no | string | The name of the variable that should be set based on the calculated value of the action. |
if |
no | if | A condition that must be met in order for the action to be executed. |
{
"timing": "glide",
"start-value": { "voltage": -3 },
"end-value": { "variable": "glide-end-voltage" },
"output": { "index": 9, "port": 6 },
"if": { "ne": [
{ "voltage": 0 },
{ "variable": "glide-condition" }
] }
}
An action with the gate
timing can be used to generate a gate signal on one of the output ports: it will set the output port voltage to 10v when the action starts, and will change it to 0v as the action progresses. By default, the change to 0v will be done when the segment that contains the action has completed half of its duration. The gate-high-ratio
property can be used to change this position, with 0
moving it to the start of the segment, 1
moving it to the end of the segment and 0.5
matching the halfway point of the segment duration.
The voltage of a gate action must always be sent to an output port.
Just like the other action types, a gate action can be made conditional using an if property.
property | required | type | description |
---|---|---|---|
output |
yes | output | The output port that will receive the gate signal |
gate-high-ratio |
no | unsigned float | The position when the gate signal should go from high to low. Must be a value between 0 and 1 , with 0.5 aligning with half of the segment duration. Defaults to 0.5 |
if |
no | if | A condition that must be met in order for the action to be executed. |
{
"timing": "gate",
"gate-high-ratio": 0.75,
"if": { "ne": [
{ "voltage": 0 },
{ "variable": "gate-condition" }
] }
}
The if object provides conditional functionality for actions and asserts either by comparing two values with each other or by checking the result of two child if conditionals using logical operators. A condition can either evaluate to true or false. The result of the condition can then enable or disable an action, or trigger the assert that the if was used in.
Each if object must contain exactly one comparison or logical operator.
Using one of the following properties in the if object will cause the matching comparison to be performed:
eq
: returns true if two values are equal (with an optional tolerance
),ne
: returns true if two values are not equal (with an optional tolerance
),lt
: returns true if the first provided value is less than the second provided value,lte
: returns true if the first provided value is less than or equal to the second provided value,gt
: returns true if the first provided value is greater than the second provided value,gte
: returns true if the first provided value is greater than or equal to the second provided value.The value of the comparison property must be set to an array of exactly two value objects. When the if is triggered, the current voltage of the provided values will be calculated, and the results will be compared with each other.
For the eq
and the ne
comparisons, an optional tolerance
can be can be provided. If provided, the two values will be considered equal (or not equal) if the difference between the two values falls within that tolerance. Since voltage calculations within VCV Rack can result in small rounding errors, providing a small tolerance
value may be needed when comparing two values for (in)equality.
property | required | type | description |
---|---|---|---|
eq |
no | value array with 2 values | Checks that the two provided values are equal, with an optional tolerance |
ne |
no | value array with 2 values | Checks that the two provided values are not equal, with an optional tolerance |
lt |
no | value array with 2 values | Checks that the first provided value is less than the second. |
lte |
no | value array with 2 values | Checks that the first provided value is less than or equal to the second. |
gt |
no | value array with 2 values | Checks that the first provided value is greater than the second. |
gte |
no | value array with 2 values | Checks that the first provided value is greater than or equal to the second. |
tolerance |
no | unsigned float | Specifies how much the two values are allowed to differ while still being considered equal (for an eq operator) or not equal (for an ne operator). |
Some examples of if usage with comparison operators within an action:
{
"if": {
"eq": [
{ "voltage": 4.2 },
{ "variable": "my-input-variable"}
],
"tolerance": 0.00001
},
"set-variable": { "my-output-variable": 6.9 }
}
{
"if": {
"lt": [
{ "voltage": 4.2 },
{ "variable": "my-input-variable"}
]
},
"set-variable": { "my-output-variable": 6.9 }
}
A logical operator allows two child ifs to be combined. Following logical operators can be used:
and
: returns true if the two child ifs both evaluate to true,or
: returns true if at least one of the child ifs evaluates to trueThe value of the logical operator property must be set to an array of exactly two child if objects. More complex conditional can be constructed by nesting multiple levels of logical if operators.
property | required | type | description |
---|---|---|---|
and |
no | if array with 2 ifs | Checks that the two provided ifs both evaluate to true |
or |
no | if array with 2 ifs | Checks that at least one of the provided ifs evaluates to true |
A single-level logical if in an action:
{
"if": {
"and": [
{
"eq": [
{ "voltage": 4.2 },
{ "variable": "my-input-variable-1" }
]
},
{
"gt": [
{ "voltage": 3.45 },
{ "input": { "index": 6 } }
]
}
]
},
"set-variable": { "my-output-variable": 9.9 }
}
An and
logical operator with a child or
logical operator as first child conditional
{
"if": {
"and": [
{
"or": [
{
"eq": [
{ "voltage": 2.1 },
{ "variable": "my-input-variable-1" }
]
},
{
"eq": [
{ "voltage": 4.2 },
{ "variable": "my-input-variable-1" }
]
}
]
},
{
"gt": [
{ "voltage": 3.45 },
{ "input": { "index": 6 } }
]
}
]
},
"set-variable": { "my-output-variable": 9.9 }
}
The set-value is used within an action to update the voltage of one of the TimeSeq outputs.
The voltage to use is determined by the value
property, while the port (and channel) on which the voltage should be updated is determined by the output
property.
The voltage will be immediately assigned to the output as part of the executed action, so any subsequent value in the script that references that output will receive the updated voltage once the set-value was performed.
property | required | type | description |
---|---|---|---|
value |
yes | value | The value that will determine the voltage to use. |
output |
yes | output | The output port (and channel) to which the voltage should be applied. |
An example of a set-value within an action:
{
"timing": "end",
"set-value": {
"value": { "voltage": 5.6 },
"output": { "index": 7, "channel": 8 }
}
}
The set-variable is used within an action to update an internal TimeSeq variable that can then be referenced by other values within the script.
The voltage to use is determined by the value
property, while the name
property determines the name of the variable that should be updated.
When a variable is set to a voltage using a set-variable action, that variable will keep that voltage value as long as the script keeps running. Pausing and resuming a script will not clear existing variables. Resetting a script, loading a new script or restarting VCV Rack will cause existing variables to be removed/cleared.
Since unknown variables will default to 0V, setting a variable to 0V will be the same as removing that variable from the list of currently known variables.
property | required | type | description |
---|---|---|---|
value |
yes | value | The value that will determine the voltage to use. |
name |
yes | string | The name of the variable to which the voltage should be assigned. |
An example of a set-variable within an action:
{
"timing": "start",
"set-variable": {
"value": { "voltage": 3.14 },
"name": "a-piece-of-pi"
}
}
The set-polyphony is used within an action to update the number of polyphonic channels on an output port.
The port on which the number of channels should be updated is determined by the index
property. The number of channels that the port should have is determined by the channels
property. Setting the number of channels to 1
will make the port monophonic. A port can have up to 16
channels.
The output ports can be addressed by their number label as it is visible on the UI, so the index
property can go from 1
up to (and including) 8
When changing the number of channels on an output port, the voltages that were previously assigned to channels of that output will be remembered. Lowering the number of channels of a port and subsequently increasing the count again will not clear previously assigned voltages.
property | required | type | description |
---|---|---|---|
index |
yes | unsigned number [1-8] | The output port on which the number of channels should be updated. |
channels |
yes | unsigned number [1-16] | The number of channels that should be available on the output port. |
An example of a set-polyphony within an action:
{
"timing": "start",
"set-polyphony": {
"index": 3,
"channels": 12
}
}
The set-label is used within an action to update text label of the output port in the tooltip when moving the mouse over the port. This is a purely aesthetic action to allow easier identification of output ports in the VCV Rack UI. Since setting the label of an output port in VCV Rack requires memory allocations (i.e. has a minor performance overhead), this action should not be executed repeatedly in a script. This action is intended to be used during script setup (e.g. in the global-actions of the script element).
The port on which the label should be updated is determined by the index
property. The label
property should contain the text to use.
The output ports can be addressed by their number label as it is visible on the UI, so the index
property can go from 1
up to (and including) 8
property | required | type | description |
---|---|---|---|
index |
yes | unsigned number [1-8] | The output port on which the label should be updated. |
label |
yes | string | The label to assign to the output port. |
An example of a set-label within an action:
{
"timing": "start",
"set-label": {
"index": 5,
"label": "My Fifth Script Output"
}
}
Asserts allow TimeSeq to be used as a module to test the behaviour of other modules. The assert action allows a check to be performed on an if condition, and if that condition is not met, it will result in an assert warning becoming active on the module UI. By writing a script that sends varying voltages through the TimeSeq outputs to another module, and then sending the output of that other module back into the inputs of TimeSeq, the assert action can subsequently verify that the other module behaved as expected.
The expect
property will identify the if condition that will be checked. If this condition evaluates to false, an assert with the specified name
will be triggered on TimeSeq. The stop-on-fail
property specifies if the occurrence of a failed assert should also pause TimeSeq, or if the script should continue running.
While the assert is mainly intended to be used to verify voltages on the input ports of TimeSeq, the expect
condition can be used to compare any types of values.
See assert for more details on how they are handled in the UI.
property | required | type | description |
---|---|---|---|
expect |
yes | if | The condition that must evaluate to true. If it evaluates to false, an assert will be triggered on TimeSeq. |
name |
yes | string | The name of the assert that will be triggered on TimeSeq if the condition evaluates to false. |
stop-on-fail |
no | boolean | When set to true , TimeSeq will pause the script execution if this assert is fired. Defaults to true . |
An example of a set-value within an action:
{
"timing": "start",
"assert": {
"expect": {
"lt": [
{ "voltage": 4.2 },
{ "input": { "index": 3 }}
],
"name": "input 3 to high"
}
}
}
Throughout the TimeSeq script, whenever a voltage is needed, a value is used to provide different ways to determine that voltage value:
Since voltage
values are usually expected to be between -10V and 10V, the constant voltage
value will by default be limited to this range. In some scenarios (e.g. when specifying segment lengths in variable-length durations), there may be a need to specify a constant value outside this range. The range check on a voltage
value can be disabled by setting the no-limit
property of a value to true
.
When the note
property is used, it must be a 2 or 3 character string, where the first character specifies the note name (A-G), the second specifies the octave (0-9) and the third (optional) character can either use +
to indicate a sharp, or -
to indicate a flat. E.g: C4+
will result in the 1V/Oct value of a middle C#, while a A3-
will result in an A flat below middle C.
If a variable
property is used and no variable with a matching name was previously set using a set-variable *action, 0V will be used instead.
Additional mathematical operations are possible on a value using the calc
property. This allows simple calculations to be performed by either adding, subtracting, dividing or multiplying this value with another value. The calc
property expects a list of calc objects. Even if only one calculation is to be performed, it should still be supplied as a list (with one element). The calculations will then be executed in the order that they appear in the list. While voltage values are usually expected to fall into the -10V to 10V range in VCV Rack, calculations will not enforce this limit, and the result of the calculation can fall outside of that range.
Using the quantize
property, a value can optionally be set to quantize to the nearest 1V/Oct note value. If enabled, quantization of the voltage value will be done after the calc operations have been applied to the voltage value. If more control is needed over the quantization , such as quantizing to a specific scale or a set of custom voltages, a calc with a quantize
operation should be used instead.
Exactly one of the voltage
, note
, variable
, input
, output
or rand
properties must be specified for a value.
Note: values always resolve into a voltage, which is then used by the object that contains the value. The source of the value voltage will not be tied to the target of that value. E.g. if an action sets the voltage of an output port using a value that is based on the voltage of an input port, the voltage to use will be determined when the action is executed. If the voltage on the input port changes afterwards, the voltage of the output port will not be changes automatically to the updated voltage of the input port. The set-value action will have to be re-executed in order for the output port to update again.
property | required | type | since | description |
---|---|---|---|---|
voltage |
no | float | An exact constant voltage value between -10 and 10 . See also Shorthand Value Notation for a shortened version for voltage values. |
|
no-limit |
no | boolean | 1.1.0 | Can only be used in combination with voltage . When set to true , the default check that enforces a voltage value between -10 and 10 will be disabled. |
note |
no | string | A note that will be translated in the corresponding 1V/Oct voltage. See the description above for the format. See also Shorthand Value Notation for a shortened version for note values | |
variable |
no | string | The name of the variable to use. | |
input |
no | input | Reads the current voltage from one of the TimeSeq inputs. | |
output |
no | output | Reads the current voltage from one of the TimeSeq outputs. | |
rand |
no | rand | Uses a random voltage value (within a specified voltage range). | |
calc |
no | calc list | Allows mathematical operations to be applied to the voltage of this value, using the voltage of another value. | |
quantize |
no | boolean | If set to true , the voltage of this value will be quantized to the nearest 1V/Oct note value after any optional calculations have been performed. If set to false , the voltage value will be used as-is after any optional calculations have been performed. Defaults to false . |
A constant voltage value:
{ "voltage": 3.14 }
A constant voltage value expressed as a note:
{ "note": "D5+" }
The voltage of channel 4 on input port 3, multiplied by 2:
{
"input": { "index": 3, "channel": 4 },
"calc": [
{ "mult": { "voltage": 2 } }
]
}
The voltage of channel 8 on output port 5, with a random value between 0.5 and 1 added to it, and subsequently multiplied by 2:
{
"output": { "index": 5, "channel": 8 },
"calc": [
{
"add": {
"rand": {
"lower": 0.5,
"upper": 1
}
}
},
{ "mult": { "voltage": 2 } }
]
}
To allow easier writing of fixed values, the TimeSeq JSON script allows voltage
and note
values to be written using a shorthand notation:
{ "voltage": 6.9 }
can be shortened to its float value instead: 6.9
{ "note": "E4" }
can be shortened to its string note value instead: "E4"
This way, following set-value actions:
{
"set-value": {
"value": { "voltage": 3.14 },
"output": { "index": 6 }
}
},
{
"set-value": {
"value": { "note": "F4" },
"output": { "index": 7 }
}
}
Can be shortened using both shorthand value and shorthand output notation as:
{
"set-value": {
"value": 3.14,
"output": 6
}
},
{
"set-value": {
"value": "F4",
"output": 7
}
}
This shorthand notation can be used in all places where voltage
or note
values are used, except when declaring a re-usable variable as a direct child of the component-pool values
list (where the full value notation must be used since an id
property must also be specified).
An input identifies a channel on one of the input ports of TimeSeq, either to read a voltage from it in a value or to monitor it for input-triggers.
The input port is identified by the index
property, using a number from 1
to 8
. The channel
property identifies which (polyphonic) channel to use. Ports can have up to 16 channels. If no channel
property is specified, the first channel of the port will be used. When working with monophonic input signals, either the channel
property can be omitted, or it can be set to 1
(since in VCV Rack, a monophonic signal is considered to be a signal containing one channel).
Note that TimeSeq will not validate how many channels are present on the input port. If a channel is requested that is outside of the current polyphonic channel range of the input signal, TimeSeq will use whatever value VCV Rack returns for that channel (usually 0V).
property | required | type | description |
---|---|---|---|
index |
yes | unsigned number | The index of the port from which to retrieve a voltage. Must be between 1 and 8 . See Shorthand Input Notation for a shortened way to write inputs with only an index property. |
channel |
no | unsigned number | The channel on the input port from which to retrieve the voltage, as a number between 1 and 16 . Defaults to 1 |
The third input port, using channel 1 since no channel
property is specified:
{
"index": 3
}
The fifth input port, using channel 15:
{
"index": 5,
"channel": 15
}
To allow easer writing of inputs, the TimeSeq JSON script allows inputs for which no channel
is specified (i.e. monophonic inputs or channel 1
on polyphonic inputs) to be written using a shorthand notation: a full { "index": 5 }
or { "index": 5, "channel": 1 }
notation can be shortened to 5
(i.e. just the index value).
This way, following set-value action:
{
"set-value": {
"value": { "input": { "index": 3 } },
"output": { "index": 6 }
}
}
Can be shortened using both shorthand input and shorthand output notation as:
{
"set-value": {
"value": { "input": 3 },
"output": 6
}
}
This shorthand notation can be used in all places where inputs are used, except when declaring a re-usable input as a direct child of the component-pool inputs
list (where the full input notation must be used since an id
property must also be specified).
An output identifies a channel on one of the output ports of TimeSeq, either to assign a voltage to it through an action, or to read a voltage from it using a value.
The output port is identified by the index
property, using a number from 1
to 8
. The channel
property identifies which (polyphonic) channel to use. Ports can have up to 16 channels. If no channel
property is specified, the first channel of the port will be used. When working with monophonic output signals, either the channel
property can be omitted, or it can be set to 1
(since in VCV Rack, a monophonic signal is considered to be a signal containing one channel).
Note that TimeSeq will not validate how many channels are present on the output port. Assigning a value to a channel that is outside the current polyphonic channel count will not result in more channels becoming active on that output. TimeSeq will however remember any voltage updates done on all channels (even if they are outside of the current polyphonic channel count), and will assign those voltages to the channels if the channel count is changed afterwards using a set-polyphony action. Similarly, if a voltage is assigned to a channel that is outside of the current polyphonic channel count, a value that references that channel on the output port will still return the value that was assigned to it.
When a script is loaded or reset, all output ports of TimeSeq will be set to monophonic mode (i.e. have 1 channel), and all voltages on the output ports will be set to 0 volts.
property | required | type | description |
---|---|---|---|
index |
yes | unsigned number | The index of the port from which to retrieve or on which to set a voltage. Must be between 1 and 8 . See Shorthand Output Notation for a shortened way to write outputs with only an index property. |
channel |
no | unsigned number | The channel on the output port from which to retrieve or on which to set the voltage, as a number between 1 and 16 . Defaults to 1 |
The third output port, using channel 1 since no channel
property is specified:
{
"index": 3
}
The fifth output port, using channel 15:
{
"index": 5,
"channel": 15
}
To allow easer writing of outputs, the TimeSeq JSON script allows outputs for which no channel
is specified (i.e. monophonic outputs or channel 1
on polyphonic outputs) to be written using a shorthand notation: a full { "index": 5 }
or { "index": 5, "channel": 1 }
notation can be shortened to 5
(i.e. just the index value).
This way, following set-value action:
{
"set-value": {
"value": { "voltage": 3.14 },
"output": { "index": 6 }
}
}
Can be shortened using both shorthand output and shorthand value notation as:
{
"set-value": {
"value": 3.14,
"output": 6
}
}
This shorthand notation can be used in all places where outputs are used, except when declaring a re-usable output as a direct child of the component-pool outputs
list (where the full output notation must be used since an id
property must also be specified).
The rand allows random voltages to be generated for usage in a value.
The generated random value will be between the lower
and upper
values. If the runtime calculation of the values results in a upper
value that is below the lower
value, the rand will swap their meaning for the random voltage generation.
property | required | type | description |
---|---|---|---|
lower |
yes | value | The lowest voltage that can be generated. |
upper |
yes | value | The generated random value will be below this voltage. |
{
"rand": {
"lower": { "voltage": -5 },
"upper": { "variable": "the-upper-bounds" }
}
}
Allows calculations to be performed on values. A value can contain a list of calculations. First the voltage of the value itself will be determined. Subsequently, each calculation modifies the value (e.g. adds or subtracts another value from the current voltage).
To safeguard against calculation errors, a division by zero will result in 0V and a remainder after division by zero will also result in 0V.
The possible operations that are available are:
add
for adding a value to the current voltage,sub
for subtracting a value from the current voltage,mult
for multiplying the current voltage with a value,div
for dividing the current voltage by a value,max
for determining the highest of two values,min
for determining the lowest of two values,remain
for calculating the remainder after division by another value,trunc
for getting the non-decimal part of a value,frac
for getting the decimal part of a value,round
for rounding the value up, down or to the nearest non-decimal number,quantize
for quantizing to a tuningsign
for enforcing that the sign of a value is either positive or negative.vtof
for converting a 1V/Oct value into a frequencyWhile multiple calcs can be added to the calculation list of a value, each calc within that list must specify exactly one mathematical operation.
property | required | type | since | description |
---|---|---|---|---|
add |
no | value | Adds a value to the current voltage. | |
sub |
no | value | Subtracts a value from the current voltage. | |
mult |
no | value | Multiplies the current voltage with a value. | |
div |
no | value | Divides the current voltage by a value. | |
max |
no | value | 1.1.0 | Compares the current voltage with the supplied value and uses the higher of the two. |
min |
no | value | 1.1.0 | Compares the current voltage with the supplied value and uses the lower of the two. |
remain |
no | value | 1.1.0 | Divides the current voltage by a value and uses the remainder after division. |
trunc |
no | boolean | 1.1.0 | Removes the decimal part of the current voltage, keeping only the whole number. The result keeps the same sign (positive or negative) as the original voltage. Must be set to true . |
frac |
no | boolean | 1.1.0 | Removes the whole number part of the current voltage, keeping only the decimal part. The result keeps the same sign (positive or negative) as the original voltage. Must be set to true . |
round |
no | string | 1.1.0 | Can be either up to round up, down to round down or near to round to the nearest whole number. |
quantize |
no | tuning | 1.1.0 | Quantizes the current voltage to the nearest voltage in the specified tuning’s notes . |
sign |
no | string | 1.1.0 | Can be set to either pos or neg . Keeps the current voltage’s value but forces it to be positive or negative, depending on which one is specified. |
vtof |
no | boolean | 1.1.0 | Interprets the current voltage as a 1V/Oct value and returns the corresponding audio frequency value. Must be set to true . |
The voltage of channel 4 on input port 3, multiplied by 2:
{
"input": { "index": 3, "channel": 4 },
"calc": [
{ "mult": { "voltage": 2 } }
]
}
The voltage of channel 8 on output port 5, with a random value between 0.5 and 1 added to it, and subsequently multiplied by 2:
{
"output": { "index": 5, "channel": 8 },
"calc": [
{
"add": {
"rand": {
"lower": 0.5,
"upper": 1
}
}
},
{ "mult": { "voltage": 2 } }
]
}
The maximum of either channel 4 on input 3, or channel 5 on input 4:
{
"input": { "index": 3, "channel": 4 },
"calc": [
{ "max": { "input": { "index": 4, "cannel": 5 } } }
]
}
The voltage on input 6, quantized to the tuning with id c-maj-pent
, and subsequently 1V (i.e. 1 octave) added to it:
{
"input": 6,
"calc": [
{ "quantize": { "ref": "c-maj-pent" } },
{ "add": 1 }
]
}
The decimal part of the voltage that is currently stored in variable my-voltage
:
{
"variable": "my-voltage",
"calc": [
{ "frac": true }
]
}
While a value can be quantized to the nearest semitone using its quantize
property, tunings allow values to be quantized to a scale. The notes
property of a tuning contains the list of notes that should be quantized towards. Notes can be expressed in two ways:
+
for a sharp and -
for a flat. E.g.: A-
, F+
, C+
, B-
, G
Quantization of values is always done in the octave range of the original value. As such, which supplying a tuning note as a float value, only the decimal part of the quantization note will be taken into account.
To quantize a value to a tuning, a calc must be added to the value, using the tuning as calc quantize
property, either directly inline or through a ref
. The value will then be quantized up or down towards the nearest notes
entry in the tuning (ignoring the octave information of the value).
Tunings were introduced in TimeSeq script version 1.1.0.
property | required | type | description |
---|---|---|---|
id |
yes | string | The identifier of the tuning. |
notes |
yes | string/float list | The list of notes to quantize to, either as a float or a string as described above. |
A tuning for a C minor pentatonic scale, with the notes specified using their note names:
{
"id": "c-minor-pentatonic",
"notes": [ "c", "e-", "f", "g", "b-" ]
}
The same C minor pentatonic scale, but this time using a combination of note names and 1V/Oct float values:
{
"id": "c-minor-pentatonic",
"notes": [ 0, "e-", "f", 0.5833, "b-" ]
}
A tuning that uses 1V/Oct values that don’t follow the usual semitone notes:
{
"id": "non-semitone-tuning",
"notes": [ 0, 0.1579, 0.3158, 0.4211, 0.5789, 0.7368, 0.8947 ]
}