Welcome to the Ozone documentation!

Ozone is a dataflow programming language. You'll want to first acquaint yourself with its core concepts before delving into its use — start with reading up on Patch.

Ozone was written by Evan Balster beginning in 2012 for use SoundSelf.

1. Patch

Patch is the basic building block in Ozone. A wide variety of built-in patches are available for various purposes, and it is possible to invent your own with a little creativity
Ozone is a dataflow language, meaning it models a flow of information — Signals— though processing pathways within a program. These pathways consist of Patches, each of which performs some combination of generating, transforming, inputting or outputting that data. Some patches are even capable of dynamically altering the structure of the program, managing the creation, destruction and inter-connection of other patches.

A Patch consists of a few basic parts. A given patch may use only some of these:

  • Properties — Built-in values which control the behavior of native patches.
  • Ports — Named channels which move data Signals between patches. There are two types:
    • Inputs — Data flows into a patch at these entry points.
    • Outputs — Sources of data which may flow out of a patch.
  • Load command — Used to load some kind of data file into the patch.
  • Import commands — Allows communication between patches defined in different pieces of code.

Patch Syntax
A patch of a given type may be declared as follows:

Variant 1 — named patch declaration

PatchType:  patchName
{
//This patch of imaginary type PatchType may be referred to as "patchName" by other patches.
}


Variant 2 — anonymous patch declaration

PatchType
{
//This patch is anonymous and may not be directly referred to by other patches.
}


The space between the  {  brackets }  is called the patch definition elsewhere in this documentation. The various parts listed above may be manipulated by writing code within it.

1.1. Properties


Properties are intrinsic values of a Patch which control its behavior. They are separate from Signals and exist only in Ozone's native patches.

They are assigned inside a Patch using the syntax property =

They may presently assume the following types:

  • strings of text.
  • boolean values which may be true or false.
  • Numbers:
    • floating-point values (decimal numbers).
    • integers (whole numbers) which might be
      • signed, meaning they permit negative values, or
      • unsigned, meaning they do not.

It is possible for real-valued properties to be controlled by expressions, in which case their value will be automatically updated each frame. This creates a potentially confusing overlap with Inputs, which also accept expressions. As a rule of thumb, numbers which might often be assigned constant values are implemented as properties.

Example Usage

PatchType: myPatch
{
    //We can set a numeric property to a constant value...
    decimalProperty = .4;

    //...Or to some math expression, which will cause it to change every frame.
    anotherDecimalProperty = (clock * 2);

    //Not all numeric properties are decimals.
    integerProperty = 5;

    //Strings are a datatype that is unsupported by Signals but can be used with properties.
    stringProperty = "This is some text!";

    //Booleans are another.
    booleanProperty = true;
}

1.2. Ports

Ports are the points at which Signals to flow into and out of a Patch .They may be Inputs or Outputs.

A port is referred to by its name, which consists of a chain of identifiers separated by dots. For instance, velocity and circle.x are valid port-names.

Outside of the patch in which they exist, ports are referred to by the name of the patch, a dot, and the port's name. For instance, myPatch.circle.x.

Default Ports


Patches with "main" inputs or outputs conventionally name those ports default. These default ports may be referenced in shorthand by simply referring to the patch itself, or using the shortest variant of the Input or Output clause.

Sub-Ports


When a port-name contains dots, the names after the dots are called sub-ports of the names preceding them, which signify some kind of grouping. A port might be named circle.x, where x is a sub-port of circle.

Some Patches, specifically Page contain other Patches and treat them as ports, with the ports of those patches being sub-ports.

It is possible, though uncommon, for sub-port chains to be quite long. Something like page.subPage.patch.port.subPort.subSubPort is entirely possible.

1.2.1. Default


Patches with "main" input or output ports conventionally name those ports default. These default ports may be referenced in shorthand by simply referring to the patch itself, or using the shortest variant of the Input or Output clause.

For example, the LFO patch has a single input and a single output, both called "default". Therefore, if I declare an LFO by the name myLFO, I may optionally omit the default when I would otherwise type out myLFO.default or Input default.

In patches with CreatedInput  functionality, it is generally possible to create default ports, which can be very convenient.

Advanced

Default ports have several interesting uses within the Page patch. A Page's port-names are the patches inside it. When such a port is referenced as an input or output, with no sub-port names following, the patch's default port is used as normal. If a patch is called default, its default ports become the default ports for the Page itself. This can be very useful when building Custom Patches.

1.2.2. Input

An Input is a type of Port through which a Signals's data may flow into a Patch. They are generally assigned using the Input keyword within the definition of the Patch.

The input signal may come directly from the output of another patch, or may be a non-trivial Expression. When a built-in input is left undefined, it will generally assume a reasonable default value.

Some patches' inputs behave in unusual ways. These special behaviors have their own articles:

  • Created Input — Inputs created by the script-writer, which might have custom or temporary names.
  • Plural Input — Inputs which can take more than one signal in at the same port.

Input Syntax

There are three variants of the Input clause, each of which is essentially shorthand for the next.
See the Expression article for a better understanding of what code may replace  below.

Variant 1 defines an input to the patch's default port.

Input = ;


Variant 2 defines an input to a single named port.

Input portName = ;Input portName.subPort = ;

Variant 3 allows many ports to be assigned at once.

Input
{
    //Equivalent to Variant 1 example
    default = ;

    //Equivalent to Variant 2 examples
    portName = ;
    portName.subPort = ;
}


Example Usage

Timer: clock
{
    //The Timer patch has a scalar default output which we will use below.
}

LFO: myLFO
{
    //The LFO patch has a default input and a default output, both Scalar.
    //See the LFO article for further information.

    Input = clock;
}

Color: myColor
{
    //A Color has inputs "hue", "sat" and "val", and outputs "r", "g" and "b".
    //See the Color article for further information.

    Input
    {
        //Assign constants to sat and val.
        sat = .8;
        val = .6;

        //Assign a math expression to hue.
        hue = (2 * clock);
    }
}

Harmonograph
{
    Input
    {
        //Assign to a created input we call "osc", with a constant: 1.
        +osc = 1;

        //Assign to the sub-ports of our created port.  Control spatial oscillations with myLFO.
        osc.x = myLFO;
        osc.y = (1 - myLFO);

        //Control color oscillation sub-ports with the outputs of myColor
        osc.r = myColor.r;
        osc.g = myColor.g;
        osc.b = myColor.b;
    }
}

 

1.2.3. Created Input

Some patches allow Inputports to be created with custom names using an Input or Output command and the + character.

This is useful when a patch takes an arbitrary number of inputs, especially when those inputs have sub-ports supplying further information.

Syntax


Using Variant 2 or 3 of the Input syntax, add a + before the name of the port which is to be created, like so:

Alias: myAlias
{
    Input +myPort = 2.0;

    Input
    {
        //We can create a default input port, but we have to type out the name.
        +default = (1.0 / 3.0);
    }
}

It is also possible to create an input in the target port of an Output statement. Again, variant 2 or 3 must be used, but now the plus-sign needs to be in a different position.
(this example also demonstrates use of Matched Output in outputter.)

Alias: target
{
}

Alias: outputter
{
    Input
    {
        +valueA = 3;
        +valueB = 42;
    }

    Output
    {
        valueA -> +target.valueA;
        valueB -> +target.valueB;
    }
}

After this code, target should output 3 and 42 from its valueA and valueB ports, respectively.

1.2.4. Plural Input


A Plural Input is a special type of input port which accepts more than one Signal, generally combining them somehow.

This is distinguished from the general case of a Single Input, which will most often drop the old input signal when a new one is supplied.

Plural inputs are very useful with dynamic instantiation, though not exclusively.

1.2.5.  Output

Most Patchesoutput some kind of information in the course of doing their work. Those which do not display that information to the user will make it available via output Ports in the form of Signals, which may be fed into the inputs of other patches.
It is important to understand that despite the similarity in syntax, outputs are separate from inputs. The existence of an input with a given name does not guarantee an output of the same name will exist, and vice versa.

The outputs of a patch are generally defined by that patch's type or the data it loads. A few other schemes exist, however:

Using the Output keyword within the definition of a Patch will allow a signal from that patch to be routed to some destination. This is much less common than using Input declarations, but allows "later" patches to communicate to "earlier" ones, and to destinations which might otherwise have no awareness of the data source's existence.

Output Syntax


Similar to the Input clause, there are three variants, each of which is shorthand for the next.
See the Target article for a better understanding of what code may replace  below.

Variant 1 outputs from the patch's default port to some target.

Output portName -> target ;


Variant 2 outputs from a single named port.

Output portName -> target;
Output portName.subPort ->  target;

Variant 3 allows many outputs to be routed at once.

Output
{
    //Equivalent to Variant 1 example
    default -> target;

    //Equivalent to Variant 2 examples
    portName -> target;
    portName.subPort -> target;
}

 

1.2.6. Matched Output

matched output is an Output port which is created at the same time as a Created Input, with the same name. Its value will be based on the matching input in some way.

These are useful for patches which alias, collect, or process many values or signals side-by-side.

Example Usage


The following example uses the Variables patch, which supports this feature.

Variables: vars
{
    Input
    {
        +a = 1.0;
        +b = 42;
        +c = (1 / 3.1415926);
    }
}

PrintOut
{
    Input
    {
        +value_of_a = vars.a;
        +value_of_b = vars.b;
        +value_of_c = vars.c;
    }
}

 

1.3. Signal


Signal is a continuous stream of data flowing from the Patch  port of one Input  to the Output  port of another.
They are the basis of all scriptwriter-defined communication, computation and processing in the Ozone engine.

Different signal types may exhibit some differences in behavior, particularly with regard to "splitting" or "sharing" them between multiple destinations.

Currently, Ozone supports signals of the following types:

  • Scalar— A Float value which changes from frame to frame.
    • Supports mathematical expressions.
    • May be shared by any number of inputs.
  • Audio — A continuous stream of sound data which may be synthesized, processed and analyzed as it flows towards output.
    • Usually can only be used by one input.


Forthcoming signal types include:

  • Video — Geometry and pixel data which can be duplicated and post processed.

1.3.1. Scalar


A Scalar is the only type of Signal presently available in Ozone.

It is a numeric value, specifically a Float, which may change in value for each frame of program execution.

Behaviors:

  • A Scalar output may be safely bound to any number of input ports.
  • A Scalar input may be safely re-bound to a new signal while the program is running.
  • Disconnected Scalar inputs are nearly always substituted with reasonable default values.

1.4. Load

Patches which require some kind of external data may import that data with a Load command written within the patch definition.

To load a data file into a patch, place a line like this inside its definition.
Load("path/to/file.ext");

The file path should be relative to the ozone file which contains it. Thus, I write:

  • Load("picture.png"); to load a file "picture.png" in the same folder as this piece of code.
  • Load("sounds/sound.ogg"); to load a file "sound.ogg" from the "sound" folder next to my code.
  • Load("../othercode.ozone"); to load a piece of ozone source from the parent folder — ".." means "the folder above this one."


Different patches might load, for example, images, sounds, settings or other script files.

1.5. Patch List

Following is a list of native patches in Ozone. These are built into the system and written in C++; it is also possible to write your own patches in the ozone language.

See the Patch article for general information on how to put these to use.

General

  • Timer— A value which changes over time. By default, measures time in seconds.
  • Timeline — Used to query user-defined regions within multi-dimensional maps. An editor is included.
  • Alias— Collects the outputs of other patches under a single name. Also known as Group.


Meta

  • Array — Creates a variable number of instances.
  • Table — Creates a grid of instances relating one set of imports to another.
  • Spawner — Creates independent pages in response to an export trigger.
  • Joiner — Creates independent pages called "groups" which may connect with various exports over time.
  • Page— A single instance of a piece of code, which may be accessed fully.


Debudding and IO

  • Keyboard — Facilitates input from keyboard keys.
  • Midi — Facilitates input from and output to system MIDI devices, presently limited to CC values.
  • PrintOut — Displays numeric values to the screen


Math

  • Variables — Stores numeric values which update each frame. Differs very subtly from Alias.
  • Total — Maintains various sums of numbers. Intended for use with Instances.
  • Curve — Ramps values gradually over time, improving Continuity but increasing Latency.
  • LFO— A function which oscillates between two values based on some "time" variable.


Audio

  • Perilymph — A voice analysis system which identifies sung tones.
  • Voice — The original voice analysis patch. Deprecated.
  • Trioxy — A modular AM/PM synthesizer, with an external editor.
  • Texture — Plays an audio loop, whose pitch and amplitude may be varied.


Video

  • Color— Converts from HSV colorspace to RGB colorspace.
  • Background — Draws a solid color over the screen. Can be transparent or a foreground, despite its name.
  • Harmograph — A visual generator based on decaying sinusoidal oscillations of varying frequencies, axes and phases.
  • Image — A raster graphic loaded from an image file. May be blended, tiled and alpha-mapped.

1.5.1. Alias

The Alias patch, alternate name Group, may have any number ofuser-defined inputs and will output those signals directly via outputs of the same name.

It is useful for situations where it is desirable to group a number of signals and expressions under a single name, for easy access and cleaner code.

It is a common means of passing arguments to instances via the Ports command.

Inputs

<"port">

All port names are fair game for use as user-defined inputs.

Outputs

<"port">

This patch has a matched outputs for each input, which routes the signal through unmodified.

1.5.2. Timer

Timer patch maintains an output value, called the accumulator, which is advanced each frame at a per-second rate. By default, this rate is 1.0, and the Timer counts the seconds that have elapsed since its creation.

Clever usage of this Patch allows it to do more than track time — for instance, it may be used to model moving objects. Simply create a timer for each axis of movement, and set the timer's rate to the object's velocity along that axis.

Inputs


Scalar rate

The rate of change for the Timer's accumulator value.
Defaults to 1.0.

To track minutes rather than seconds, this value could be set to (1/60).

Scalar reset

If this input is non-zero, the accumulator is set to zero.
Defaults to 0.0.

To create a clock that resets to zero every sixty seconds, we could write:

Timer: myClock
{
    Input reset = (myClock > 60);
}

 

Outputs


Scalar default — defaultoutput

Outputs the value of the Timer's accumulator. By default, this is the number of seconds elapsed since creation.

1.5.3. Array

An Array maintains some number of instances of a script file, each of which is assigned a number and informed of the total.

It is possible for an Array to maintain zero, one or many copies, and the number may be adjusted as the program runs.

Properties


Float count

This number, rounded down, controls the number of instances in existence. Instances will be created or destroyed at the end of the list as necessary.

Defaults to 0

Loading


The load command specifies the script source file which will be used to create the Array's instances.

Imports


Patches specified with the Import command will be imported into every instance created by the Array.

In addition, the Array will import a special patch, named-+ index+-, into each instance it creates. This patch has the following outputs:

Scalar index

Specifies the array index of this instance, an integer ranging from 0 to count-1.

Scalar index.count

Specifies the total number of instances in the Array.

Scalar index.fraction

Equivalent to (index / index.count). It is useful for Arrays whose elements should be evenly distributed across a range.

1.5.4. Spawner

An Spawner instantiates of a script file in response to import or (more frequently) exports to the name trigger.

By default, these instances will last as long as the trigger which created them. However, it is possible for them to take control of their own lifetimes and outlast their originators. This makes the Spawner well-suited to modeling "independent" objects and processes — though as with any instantiator none will outlive the Spawner itself.

Loading


The load command specifies the script source file which will be used to create the Spawner's instances.

Imports


The patch exported to trigger will be available in created instances as trigger.

Special Definations


Instances may define a patch called SPAWN, with a numeric output called survive. If this is done, the instance will continue to exist so long as SPAWN.survive is not equal to zero. This will override the default behavior, allowing the instance to terminate while the trigger still exists, or endure long after it has vanished.

An example:

Import trigger;  //Any outputs taken from the trigger will break if we outlive it.

Timer: age
{
}

Variables: SPAWN
{
    Input +survive = (age < 10);
}


If created by a spawner, this instance would exist for exactly ten seconds before being destroyed.

1.5.5. Joiner

The Joiner is a more complex relative of the spawner. Like its cousin, it creates instances, called "groups", in response to imports or export to a special name — item. The critical difference is that it is possible for an item to connect with an already-existing group. A group may "contain" zero, one or many items.

When an item is added to the joiner, it is matched with an existing group according to the join rule. If no suitable group exists, one is created. As in spawner, groups may optionally control their own lifetime and continue existing even with zero members.

Since there is no way for groups to be directly changed by the addition of new items, connections are made and broken through the creation and destruction of a second page type, called joints. Think of these as representing an item's membership in a group, which ceases to exist when the item or group vanishes, or the item is moved to a different group.

Join Rule


Currently the join rule is that each group has a range of numeric values, GROUP.min to GROUP.max, in which the item's value JOIN.x must fit. Thus, groups are effectively arranged along a number-line. In the future this may be generalized.

Properties


Boolean dynamic

If this value is true, items will be constantly checked against their groups to see if the join rule has been broken — that is, if they've strayed outside the group's range. If so

Defaults to 0

Imports(joint)


In the joint page, the patch exported to item on the joiner will be available as item, and the group with which it has been associated will be available as group.

In order to move information between the two, it may be necessary to use the In-Out technique.

Special Definitions(group)


The Group page must define a patch called GROUP, with numeric outputs min and max, defining its range for use with the join rule. Optionally it may also define an output-+ survive+-, which functions as in Spawner.

An example:

Variables: GROUP
{
    Input
    {
        +min = -1;
        +max = 1;
    }
}


This isn't a very useful group, as its range is fixed. Generally a scheme for defining a group's range based on its initial item will be necessary.

Special Definitions(item)


Items exported to a joiner need to define an output within themselves named JOIN.x.

The value x will be compared to the group ranges described above in order to enforce the join rule.

An example:

Variables: JOIN
{
    Input +x = 5;
}


This page would be entered into a group at position 5.

1.5.6. Keyboard

keyboard exposes input from the system keyboard (if any) to the program.

It is intended as a debugging feature but there's nothing wrong with using it to make keyboard-controlled programs.

Outputs


Scalar <"key name">

Each of the keys listed below outputs a value of 1.0 if the key is being held, and 0.0 otherwise.

//Arrow keys
up, down, left, right,

//Letters and numbers
_0, _1, _2, _3, _4, _5, _6, _7, _8, _9,
a, b, c, d, e, f, g, h, i, j, k, l, m,
n, o, p, q, r, s, t, u, v, w, x, y, z,

//General purpose controls.  A few of these control built-in Ozone functions; see the Operation article.
control, shift, alt, capsLock,
space, tab, backspace, enter, escape,
home, end, pageUp, pageDown, insert, del,

//The following keys should be avoided as some control special engine functions.
f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15,
tilde,

none; //A null button which is always un-held.

 

1.5.7. Variables

The Variables patch is very similar in usage to the alias patch, but limited to use with Scalar signals. It stores them each update and outputs the stored values.

The usefulness of this may be less than obvious, and merits some explanation:

Mathematical expressions and many math patches will re-compute the value of their scalar outputs, querying their inputs, each time another patch queries for those outputs. It is legal for the output of a math formula to go to several destinations, and each of those may split in the same manner. Every split causes the formula to be evaluated again, and certain arrangements can cause them to explode exponentially. Early SoundSelf versions ran into problems where the same math was being performed tens of thousands of times per frame!

It is also possible, though uncommon, that a formula may end up collecting part of its input from a patch to which it outputs, and a loop may be created. If no caching mechanism like Variables is used, this loop creates an infinite formula which can never be solved, and hangs or crashes the program.

Inputs


Scalar port

All port-names are fair game for use as user-defined scalar inputs. Their values will be captured each update.

Outputs


Scalar port

This patch has a matching-output for each input, which produces the stored value of that input.

1.5.8. Total

Total patch maintains a set of running totals of Scalar values, via createdplural input ports.

It is useful for combining the output values of dynamic instances whose outputs cannot be directly accessed, using the output keyword.

Inputs


Scalar "port" (plural)

Once created, input ports will accept additional scalar inputs, which will be summed together to the output of the same name.

When using this patch with dynamic instances, convention is to declare the inputs in the Total patch, usually initializing them to 0 or some other desirable base value.

Outputs


Scalar "port"

These matched-outputs produce the sums of all scalars connected to the corresponding inputs.

1.5.9. Curve

The Curve patch smooths a set of Scalar values out over time. By gently ramping output values toward input values, it can turn a chaotic signal into a gentle one.

todoThis patch was named a little hastily, and its name may be changed in the future. Another proposed patch is much more well-described by the name "Curve".
todoMore curvature modes should be added, and the control values should be made framerate-independent.

This process improves Continuity — making the signal's changes gentler and preventing sudden jumps. At the same time, it induces Latency — making the signal take effect more slowly. These two effects are directly related, and will tend to increase or decrease together.

A few different types of ramping are available, as described below; their effects may be combined freely.

Properties


Float linear, up, down

Linear ramping will bring the output value toward the input at a fixed rate when they differ.

up and down control the rates of increase and decrease separately, while linear controls both at once.
These values default to 0.0.

Float damp, damp2

Asymptotic ramping pulls the output value toward the input value at a rate proportional to the difference between them.
The target value will be approached, but never quite reached — as letting values reach 0 can be important, combining a tiny amount of linear ramping can be helpful.
damp defaults to 0.0 and controls the first-order asymptotic ramp. More intuitively, it defines the fraction of the difference to be eliminated per frame.
damp2 defaults to 1.0 and can optionally be assigned a lesser value to make the ramp's rate of change also continuous. It controls a second-order asymptotic ramp, toward the value of the first one.

Inputs


Scalar "port"

All port-names are fair game for use as user-defined Scalar inputs. Their values are the targets for the ramping action each update.

Outputs


Scalar "port"

This patch has a matching-output for each input, which produces the smoothly-ramped value of that input.

1.5.10.  LFO

An LFO, short for Low Frequency Oscillator, is a mathematical function that oscillates (moves back and forth) between two values. Generally this oscillation is over time but it is possible to control it with other values.

This patch allows some control over the shape of the oscillation (with curve) and its starting point (with phase).

Input


Scalar default— default Input

The value which controls the oscillation, generally expected to be a timer.
Defaults to 0.0.

Every increase of 1.0 in this input value represents a full oscillation of the LFO.
Thus its output would be the same for inputs of 0.43, 11.43 and -2.57, for instance.

Output


Scalar  default — default Output

Returns the value of the oscillation function, as defined by the below parameters.

Properties


Float low, high

These are the two values between which the LFO will oscillate.
Default to 0.0 and 1.0 respectively; these values are useful for fading colors and sounds.

It's quite all right for high to be less than or equal to low; the names are chosen for clarity.

Float curve

This value controls the shape of the oscillation between low and high.

Defaults to 0.5, which produces a gentle sine wave motion.
A value of 1.0 will produce a triangle wave where the value moves at a constant speed, changing direction suddenly.
A value of 0.0 will produce a square wave where the value jumps between its extremes suddenly.

Values anywhere between 0.0 and 1.0 are valid and will produce motions in-between the ones described here.
Values in excess of 1.0 will produce strange motions that linger near a center value, jumping only briefly to the extremes.

Be aware that temporal aliasingmight have undesirable effects on very low or high curve values.

Float phase

Added to the default input (see below) when computing the oscillation function. Useful for controlling the starting-point.
Defaults to 0.0, which has the oscillation at its middle value, increasing.
0.25 will start the oscillation at high, while 0.75 will start it at low.
0.5 will start the oscillation in the middle, but decreasing.

1.5.11.  Perilymph

Perilymph is a pitch detection and voice analysis system used as the control scheme in SoundSelf. It is also a separate program.

this page is a work-in-progress.

This patch creates instances of code which receive information on individual voice tones it detects.

todoDue to its incomplete state and central importance in SoundSelf, this patch is very likely to be refactored in the future, possibly breaking compatibility with old code.

Properties


String toneCode

Used in lieu of a Load command

1.5.12. Sound Texture

The Texture patch plays a sound file on loop, with continuously-controlled pitch and amplification.

Changes in pitch and amplitude will ramp smoothly from frame to frame.
Multiple mechanisms for pitch changing are available, and their effects "stack".

Be cautious of using extremely high pitches as these may cause performance issues and audio aliasing. As data is no longer streamed from disk, this is less of a problem than formerly, however.
Extremely low pitches have no such drawbacks and are in fact quite a lot of fun.

todoThe term "texture" creates an unpleasant confusion with the graphics concept. This patch may be renamed or given a preferred alias in the future.
todoWhen support for Audio signals is added, this patch will no longer output directly to the speakers, instead using its default output port.

Load Command


The Load command for this Patch expects a path to a supported audio file.
Currently the only format supported is ogg.

The file will be buffered into memory by Ozone, and played from there by this patch. Only one copy of any given file will be buffered, so the cost of having multiple Textures using a single file is low.

Inputs


Scalar amp

Controls the amplification of the sound, where 1.0 is full volume and 0.0 is silence.
Defaults to 1.0.

It is possible to amplify beyond 100% volume, or to use negative values to invert the signal, which allows for interesting tricks...
Be cautious with the math; careless audio programming can easily cause hearing damage.

Scalar pitch

An offset in musical semitones (equal temperament) that affects the pitch (and tempo) of the loop.
Defaults to 0.0, meaning a shift of 0 semitones.

12.0 will double the rate of the sound and bring its pitch up an octave. -12.0 will halve the rate and drop down an octave.

Scalar rate

An alternate means of affecting the pitch and tempo of the sound. Multiplies the resampling rate.
Defaults to 1.0, meaning one times the base pitch.

2.0 will double the pitch and .5 will halve it. Zero and negative values are meaningless and will be clamped.

Properties


String fitScale, fitNotes

They must be formatted as a string containing some set of capitalized note names (A-G), possibly followed by sharps or flats: b or # Spaces are okay.
Examples: "ABCDEFG", "A B C# D E F# G", "DbBb"

The filename passed to the Load command must also contain a list of notes present in the sound. This list should be present in the name of the file itself, between the last underscore and the last dot in the filename.
Example: Load("audio/Synth_wet_BbDbF.ogg");

If all of these requirements are met, an algorithm will be run which searches for the best pitch-shift to satisfy the following goals, in descending order of importance:

  1. Make sure every note in fitNotes is present in the shifted sound.
  2. Make sure every note in the shifted sound is present in fitScale.
  3. Pitch-shift the sound as little as possible, which a slight preference for shifting downward.

Outputs


None as yet. In the future this patch will output its audio via an Audio port.

1.5.13.  Color

The Color patch facilitates conversion between color spaces. Presently, it offers just one conversion: HSV to RGB.

todo: The default color conversion model will be changed to a smoother function — a circle in colorspace rather than the jagged hexagon of conventional HSV computations.

Inputs


Scalar hue

The hue determines the bearing of the output color on the color wheel.
0.0, the default, is red. 0.333 is green. 0.667 is blue, and 1.0 is red again.
As the color wheel is cyclic, values outside (0.0, 1.0) will "wrap around".

A fuller sequence:

Red Orange Yellow Chartreuse Green Turquoise Cyan Aqua Blue Purple Fuchsia Rose Red
0.000 0.0833 0.1667 0.2500 0.3333 0.4167 0.5000 0.5833 0.6667 0.7500 0.8333 0.9167 1.0000

Scalar sat

The saturation determines the intensity of the color.
0.0, the default, produces greyscale colors. 1.0 maximizes the hues described above.

It is common artistic wisdom to avoid using multiple fully-saturated colors together.

Scalar val

The value determines how bright or dark the color is. The lower the value, the closer the color becomes to black.
1.0, the default, means the brightest color channel will have a value of 1.0. 0.0 will make the output color black.

Outputs


Scalar red, green, blue — alternately, r, g, b

The channels of the output color, as computed based on the inputs above.
Assuming sat and val are within standard ranges, these will be between 0.0 and 1.0.

1.5.14. Harmonograph


The Harmonograph is an extremely flexible visual generator which creates line figure based on a series of decaying sinusoidal oscillations along various axes, spatial and otherwise. The interference — consonances and dissonances — between the rates and phases of these oscillations create complex emergent patterns in the resulting figure.

Like any good Generator, its artistic usage is a topic far more complex and nuanced than the system itself, and outside the scope of this article. Experiment and explore.

This article is a work in progress.

1.5.15. Argument


An Argument is a value that has been passed into a function through one of its Parameter slots.

Inside function code, arguments are used by typing their name in brackets, like so:

Construct PlayLoop(String filename, Input amp)
{
    Block  //anonymous block prevents name conflicts
    {
        Loop: loop
        {
            Load([filename]);
        }

        Amp: amp
        {
            Input = loop;
            volume = [amp];
            Output -> MASTER;
        }
    }
}
Special Arguments

There are a handful of special arguments which are accessible even when not defined as function parameters.

these are tentative and may not yet be implemented.

The following are always usable, even outside of functions:
PAGE is a Patch-type argument corresponding to the Page which is currently constructing. It is similar to the older this value.
BLOCK is a Patch-type argument corresponding to the current Block(which is also a page). Outside of any block, it is the same as PAGE.
PATCH is a Patch-type argument which describes the patch currently being defined. If used outside a patch, it is invalid.
LAST is a Patch-type argument which refers to the patch which was defined last. It is very useful for chaining anonymous patches together.

Constructs have a special argument NAME which allows them to be used like patches in their own right; see that article for more information.

2. Page


Page is perhaps the most important and powerful among the native patches in Ozone. Whenever a script file is loaded up, a Page is created according to the instructions in that file.

Effectively, it represents a single instances  of a script file, and acts as a container for other patches . It is also a patch itself, and makes the inputs and outputs of all patches within it readily accessible as sun-ports .

Pages can be created by means other than the declaration of this patch, in which case their inputs and outputs are not accessible except from inside the Page:

  • When Ozone loads a script file directly. These are called main pages or programs by convention.
  • When an instance  is created dynamically by a meta patch other than Page.

Imports


This patch supports the import command. Unlike most other meta patches, it has no built-in imports to the imported Page.

Loading


Use the Load  command with a relative path to the script file which is to be created. The current script will cease parsing at this point, until the target file has been loaded completely. (In a sense it's like the child script was stuck in the middle of the parent script, except all the names created live inside the page.)

Imports should be declared before the Load command of a page, and any inputs or outputs afterwards. This is because these commands are executed in order; the Load operation is dependent on Imports, and no patches exist within the page until afterward.

Inputs and Outputs


The ports of a Page are simply the names of the Patches existing inside it, and their ports within them.

For instance, myPage.myPatch refers to a patch called myPatch inside a Page patch we have called myPage. Because no port-name was specified after myPatch, this name will refer to a default  port of myPatch, assuming one exists.

This rule allows for routing both Input  and Output  to patches inside a page, and to pages within it as well.
 

2.1. Block


A Block is a group of patches which are grouped under a parent name. More technically, it is a Page whose code is embedded in the source for another page, which imports all names from its parent.

They might be compared to namespaces, structures or classes in other programming language. Exactly which comparison is appropriate depends on how you use them!

They are defined much like patches:

Block: myBlock
{
    //Outside the block, we can refer to this patch as myBlock.vars.
    //Inside, it is simply "vars".
    Variables: vars  {Input +default = 5;}
}

PrintOut: print
{
    Input +vars = myBlock.vars;
}


Like patches, we may also define anonymous Blocks to avoid name clutter.

Block
{
    //This patch is called "vars" inside the block, but has no name in the containing patch!
    Variables: vars    {Input +a = 3;  Input +b = 4;}

    //We can refer to "vars" here, but it has no name in the containing patch!
    Variables: default    {Input +default = ((vars.a + vars.b) ^ .5);}
}

2.2. Instance

An instance is technically the same thing as a Page  — a contained set of patches  that have been created according to the instructions in an ozone script file.

The term is used with preference, however, in situations where the created page is not accessible to the page which created it. This is most commonly the case when the page is created dynamically — meaning while the program runs.

Communication


Because these pages don't exist while their creating page — called the parent — is being parsed, their outputs cannot be acquired by the parent. However, using the Output  keyword, they may route outputs from their patches to the inputs of patches which have been imported  into them. These connections will be automatically severed when the instance is destroyed.

Several patches exist which facilitate this specifically. Look out for these instance-friendly features, which can be used to combine outputs:

2.3. Block

Block is a group of patches which are grouped under a parent name. More technically, it is a page whose code is embedded in the source for another page, which imports all names from its parent.

They might be compared to namespaces, structures or classes in other programming language. Exactly which comparison is appropriate depends on how you use them!

They are defined much like patches:

Block: myBlock
{
    //Outside the block, we can refer to this patch as myBlock.vars.
    //Inside, it is simply "vars".
    Variables: vars  {Input +default = 5;}
}

PrintOut: print
{
    Input +vars = myBlock.vars;
}


Like patches, we may also define anonymous Blocks to avoid name clutter.

Block
{
    //This patch is called "vars" inside the block, but has no name in the containing patch!
    Variables: vars    {Input +a = 3;  Input +b = 4;}

    //We can refer to "vars" here, but it has no name in the containing patch!
    Variables: default    {Input +default = ((vars.a + vars.b) ^ .5);}
}

 

2.4. Import

The Import command allows patches  in the current page  to be made accessible to other Pages and instances  that it creates, effectively allowing for bi-directional communication between separate pieces of code.

This is useful for various purposes, for example:

  • Sharing Patches and information between two scripts, allowing for a connected program in multiple files.
  • Making multiple pages  from a single script, each with different inputs controlling its behavior. (This use is similar to function arguments.)
  • Capturing and combining output from dynamic instances  of scripts, whose outputs cannot be referenced directly.


Creative uses of Import are discussed further in the articles on patches which support the function. (See the meta tag.)

Import Syntax


Within a patch capable of creating instances, an import may be defined with the following syntax:

Import  =


Inside the created patches, the item referred to by  will be known as\
Note: Import commands should, as a convention, precede Load commands in code.
It may often be desirable for the internal name to match the external one, and lines such as Import clock = clock; are not unreasonable.

It is possible to import patches from inside another Page , using the dot operator, like so:
Import clock = timeModule.clock;

Under no condition, however, is it possible to import a single Port. For this reason, Alias and Variables are commonly used as imports.

Required Imports


Scripts written for use with import will generally reference  and will malfunction if it is not imported due to a typo or programming oversight. Normally, this malfunction (a missing patch-name) produces only non-critical warnings which will appear on the terminal.

The requirement may be enforced more strictly by placing the following line inside the imported file, above and outside of any patches:
Import ;
Again, to programmers familiar with other languages it's useful to think of imports as arguments. This line enforces that an argument must be supplied.

2.5. Export

The Export command allows patches  in the current Page to be imported  into other patches. It complements the Import keyword in a way similar to how Output  complements Input .

Its use in practice is often very different from Import. Specifically, it was designed for use with dynamic instantiator  patches, which often create instances in response to specific exports.

Export Syntax


The export command may only appear at page scope, outside all patch definitions.

An export may be defined with the following syntax:

Export patch-> destination.name;


Inside the patch , the item referred to by will be known as .

Frequently, dynamic instantiators will create code when they receive an export to a special name, which depends on their type.

It is possible to export patches from inside another Page , using the dot operator, like so:
Export timeModule.clock -> myArray.clock;

3. Function

function is a piece of ozone code which may be called from other locations in a program, with differences defined by its parameters . In terms of program behavior, calling a function is equivalent to substituting the called code into the call site.

As compared to older code-substitution mechanisms (pages and macros) they offer these strengths:

  • They may be embedded in the page which calls them, or imported from another page.
  • Reduces compilation and memory overhead as compared to macros — also better run-time performance in some situations.
  • The potential for more intuitive error reporting by way of a "backtrace" of function calls.


Functions are identified by the syntax level at which they function:

  • Constructs  work at the page level, and may define patches and namespaces. They may be used in place of an ozone file for a Load  command.
  • Behaviors will work at the patch level, and allow common set-up routines to be simplified.
  • Formulas will work at the expression level, allowing you to shorthand common formulas and optimize your code.

3.1. Construct

Construct is a Function  which works at the page level — that is, outside of any patch. It can define new patches and blocks, or behave as a patch in its own right.

The definition of a construct follows this pattern:

Construct functionName( parameterList)
{
    //Construct code goes here!
}


...where parameterList is as described in PArameter .

3.2. Parameter

Function  has a number of parameters — slots where pieces of data called arguments  need to be plugged in so that the function knows what to do.

There are currently four basic parameter types:

  • Input, which accepts Signal values such as ports  and expressions as arguments.
  • Patch, which accepts the name of a patch, similar to the Import  command.
  • String, which is a piece of quoted text, like "the dog ate my homework" or "audio/sine_wave.ogg".
  • Constant, which is a fixed number value like -100 or 3.14159.

Syntax

An individual parameter is defined using one of two forms:
"parameter type" "parameter name"
or

"parameter type" "parameter name" = "default value"

A function definition always has an parameter list which is a parenthesized list describing the arguments function takes, separated by commas. A typical parameter list might look like this:
Construct SoundEffect(String filename, Patch output, Signal amplification, Constant frequency) //patch code

We must call this function with each of its parameters given in the proper order, like so:
SoundEffect("audio/sine_A4.ogg", MASTER, (soundControl.amp_sine * .5), 440);

Default Values


String and Constant parameters may have default values, which will be used when you do not specify the corresponding arguments.

The following function:
Construct SoundEffect(String filename, Patch output, Signal amplification, Constant frequency = 440) {...} //code goes there
could be called like so:
SoundEffect("audio/sine_A4.ogg", MASTER, soundControl.amp_sine);
and because the last argument is not supplied ozone will assume 440 automatically. If we do specify a value, it overrides the default setting:
SoundEffect("audio/sine_A2.ogg", MASTER, soundControl.amp_low_sine, 110);

Default values never come into play if required arguments come after them in the parameter list, so it's generally best to place them at the end.