Index
Fenv
This functor defines an abstraction for using numerical functions as envelopes (function envelopes, or "fenvs"), and provides a rich set of functions/methods to generate, combine and transform these envelopes.

See testing/Fenv-test.oz for examples (using a Gnuplot interface for envelope visualisation).

NB: This functor aims for a high degree of flexilibity in envelope creation and manipulation instead of efficiency. But nowadays, machines are rather fast...

Functor

Import

Export

Define

[class info]

Defines a data structure for envelopes based on the notion of numeric functions (a function envelope or "fenv").

class Fenv
   feat !FenvType end

fun{IsFenv X}
Returns true if X is a Fenv instance and false otherwise.


fun{FenvSeq FenvsAndPoints}
Combines an arbitrary number of fenvs to a single fenv. Expects its args as a list in the form [fenv num fenv num ... fenv]. The numbers between the fenvs specify the start resp. end point of a certain fenv. All numbers should be between 0--1 (exclusive).


fun{FuncsToFenv Funcs Args}
Converts a list of unary numeric functions to a single fenv. The arguments min and max a given for all functions and the functions are equally spaced in the fenv.


fun{Osciallator MyFenv N}
Defines a new fenv by repeating givenm fenv n times.


fun{PointsToFenv Func Points}
Converts a list of points into a single env. A point is an x-y-pair as [Xi Yi]. X values of the points range from 0--i (including), e.g., [[0.0 Y1] [X2 Y2] ... [1.0 Yn]]. The function Func defines the shape of the fenv segments and must return a fenv. It expects a list of four numeric arguments, which describe the start and end points of the segment in the form [X1 Y1 X2 Y2].


fun{LinearFenv Points}
Defines a fenv which interpolates the given points by a linear function. Expects a list of x-y-pairs as [[0.0 Y1] ... [1.0 Yn]].


fun{SinFenv Points}
Defines a fenv which interpolates the given points by a sin function, using a full wave length. This results in a fenv without edges, however, this fenv is rather 'curvy'. Expects a list of x-y-pairs as [[0.0 Y1] ... [1.0 Yn]].
NB: in the lisp library, this was macro sin-env1.


fun{SinFenv2 Points}
Defines a fenv which interpolates the given points by a sin function. Using only the intervals [0,pi/2] and [pi, 3pi/4], which results in edges but is less 'curvy' than SinFenv. Expects a list of x-y-pairs as [[0.0 Y1] ... [1.0 Yn]].
NB: in the lisp library, this was macro sin-env.


fun{ConstantFenv Y}
Returns Fenv which outputs Y (a float) for any X.


fun{SinOsc N Args}
Defines a fenv of sin shape with n periods. Args are mul and add, as for ScaleFenv.


fun{Saw N Args}
Defines a fenv of saw shape (ascending) with n periods. Args are mul and add, as for ScaleFenv.


fun{Triangle N Args}
Defines a fenv of triangle shape with n periods. Args are mul and add, as for ScaleFenv.


fun{Square N Args}
Defines a fenv of square shape with n periods. Args are mul and add, as for ScaleFenv.


fun{Pulse N Args}
Defines a fenv of pulse shape with n periods. Args are min (lowest value), max (highest value), and width (pulse width between 0.0 and 1.0). The oscillator starts with the highest value.


fun{ReverseFenv MyFenv}
Reverses MyFenv (i.e. flips it at x=0.5).
NB: ReverseFenv is defined only for the valid Fenv domain 0.0 .. 1.0.


fun{InvertFenv MyFenv}
Inverses MyFenv (i.e. flips it at y=0.0).


fun{Reciprocal MyFenv}
Returns a Fenv which is the reciprocal of the given Fenv, i.e., 1/fenv.


fun{CombineFenvs CombiFunc Fenvs}
Returns a fenv which combines the given fenvs with an n-ary numeric function. Fenvs is a list which consists of fenvs and floats (representing constant fenvs) in any order. The combine-func expects a list with as many floats as correspond to Fenv values (in their order and at the same x), and returns a float.


fun{ScaleFenv MyFenv Args}
Scale MyFenv with Args: arg mul is factor and arg add is summand (addend).


fun{RescaleFenv MyFenv Args}
Returns a new Fenv which rescales the given y-range of MyFenv (defaults: oldmin:~1.0, oldmax:1.0) into a new range (defaults: newmin:0.0, newmax:1.0).
All these four arguments can be fenvs as well.

!! NB: RescaleFenv is buggy. Problems with neg. numbers (see examples).


fun{Waveshape Fenv1 Fenv2}
Returns a fenv which reads Fenv1 'through' Fenv2: the y value of Fenv2 (at a given x value) is used as x for Fenv1. to access the y of Fenv1 (the y of Fenv1 is returned). Compared with waveshaping in signal processing, Fenv1 is the "transfer function" and Fenv2 is the "input signal".
NB: Take care to keep the output of fenv2 in interval [0,1].

NB: for more simple use, I should think about more complex def which allows for Fenv2 values going beyond the interval [0,1] (or be automatically scaled into that interval). I could use a plain function as transfer function, but using the tools for generating fenvs can be helpful. Alternatively, I can simply remove the condition which restricts fenvs to [0,1].


fun{FenvSection MyFenv Args}
Returns fenv which is a section of given fenv. y value at 0/1 of returned fenv is y value of given fenv at min/max. Both min and max must be in the interval [0, 1].


fun{Integrate MyFenv Step}
Returns the integral fenv of fenv.
Performs numerical integration internally whenever a value of the returned fenv is accessed, which can be computationally expensive.
Step (a float in [0.0 0.5]) specifies the resolution of the numeric integration: the smaller Step, the more accurate the integration and the more expensive the computation. Step=0.01 results in 100 "function slices".

Note: implementation currently always uses Simpson's rule rule for the approximation (based on a polynomial of order 2, pretty good :), it case this is too computationally expensive, could be made user-controllable if necessary (see implementation).


fun{TempoCurveToTimeMap MyFenv Step}
Transforms a fenv expressing a normalised tempo curve into a fenv expressing a normalised time map. Step (a float) specifies the precision (and efficiency!) of the transformation, see Integrate's doc for details. A tempo curve expresses a tempo factor, i.e., f(x) = 1 results in no tempo change. A normalised time map maps score time to performance time.
Private Terminology: normalised time shift functions, time map functions and tempo curves: fenvs where x values denote the score time (usually of a temporal container) which is mapped into [0,1]: 0 corresponds to the container's start time, and 1 corresponds to the container's end time. See ContainerFenvY.
NB: normalised time map fenvs cannot be combined by function combination (x values for fenvs are always in [0,1]). Instead, either combine tempo curve and time shift fenvs, or combine plain and un-normalised time map functions (i.e. no fenvs).


fun{TempoCurveToTimeShift MyFenv Step}
... this is probably not a good idea, but works for certain cases.


fun{TimeShiftToTimeMap TS}
Expects a fenv representing a normalised time shift function and returns a fenv representing a normalised time map function. A time shift function expresses how much is added to a score time to yield a performance time, i.e., f(x) = 0 causes performance time to be score time. A normalised time map maps score time to performance time.
Private Terminology: normalised time shift functions, time map functions and tempo curves: fenvs where x values denote the score time (usually of a temporal container) which is mapped into [0,1]: 0 corresponds to the container's start time, and 1 corresponds to the container's end time. See ContainerFenvY.
NB: normalised time map fenvs cannot be combined by function combination (x values for fenvs are always in [0,1]). Instead, either combine tempo curve and time shift fenvs, or combine plain and un-normalised time map functions (i.e. no fenvs).


fun{TimeMapToTimeShift MyFenv}
... this is perhaps not a good idea, but works for certain cases.


fun{ConcatenateTempoCurves Specs}
Concatenates a sequence of successive tempo curve fenvs. Specs is a list of pairs and has the form [Fenv1#Dur1 Fenv2#Dur2 ... FenvN#DurN], where FenvI is a tempo curve fenv and DurI (a float) is the score time duration of this tempo curve. Returned is a single tempo curve fenv.
NB: in most use-cases the sequence of successive tempo curve fenvs should start at score time 0 and span over the entire score so that the global tempo curve fenv is the result. If you concatenate a tempo curve sequence which does not start at score time 0, you should decide whether the resulting tempo curve fenv starts at the performance or score start time of its first sub-tempo curve (i.e., whether a smooth continuation of previous tempo changes is intended or not).


fun{TemporalFenvY MyFenv Start Duration MyTime}
Accesses the y-value of MyFenv which starts at time point Start (a float) for time interval Duration (a float). The fenv x-value 0.0 corresponds to the start time and the fenv x-value 1.0 coresponds to the resulting end time. MyTime (a float) is any time between the start and end time. All times are score times measured in the same time unit.


fun{ItemFenvY MyFenv MyItem MyTime}
Accesses the y-value of MyFenv which is associated with a temporal item MyItem. The fenv x-value 0.0 corresponds to the item's start time and the fenv x-value 1.0 coresponds to the item's end time. MyTime (a float) is any time between MyItem's start and end time. MyTime is a score time measured in the time unit of MyItem.


fun{FenvToMidiCC MyFenv N Track StartTime EndTime Channel Controller}
Transforms a Fenv into a list of continuous MIDI controller events. N events are output between StartTime and EndTime (two ints, given in MIDI ticks) at Channel (an int).
Controller denotes which controller is output. Possible values are one of the atoms pitchbend, and channelAftertouch, or one of the pairs cc#Number (Number is the controller number) and polyAftertouch#Note (Note denotes the note pitch).
Finally, Controller can be a function expecting 4 arguments and returning a MIDI event. For example, the volume Controller can be defined as follows
fun {$ Track Time Channel Value}
{Out.midi.makeCC Track Time Channel 7 Value}
end
NOTE: no implicit support for any tempo curves etc. Instead, adapt StartTime and EndTime (and possibly transform MyFenv) outside FenvToMidiCC.


fun{ItemFenvToMidiCC MyFenv N Track MyItem Channel Controller}
Like FenvToMidiCC, but here the Fenv is associated with a temporal item MyItem, whose start and end times are taken.
NOTE: no support for any tempo curves etc.


fun{ItemFenvsToMidiCC MyItem Args}
Expects a temporal item which defines fenvs in a info-tag 'fenvs', and returns a list of continuous MIDI controller events for all its fenvs. Each fenvs is defined by a pair Controller#Fenv, where Controller can take all values defined for FenvToMidiCC. Fenvs directly specify the controller values (e.g., if Controller is pitchBend, then the Fenv range is 0.0 to 16383.0, and the value 8192.0 means no pitchbend). Note that for any controller only a single Fenv should be defined at any time (otherwise they conflict with each other).
Example: fenvs((cc#1)#{Fenv.linearFenv [[0.0 0.0] [1.0 127.0]]})

Args:
ccsPerSecond: how many CC events are created per second (a float).
track: MIDI track to output, default 2 (suitable for more cases)
channel: midi channel to output, default nil (if nil, MIDI note object CCs are output to its channel and all other to channel 0)

Timeshift fenvs affect the start and end of the continuous MIDI controller events, but not their "spacing".


fun{ItemTempoCurveToMidi MyItem Args}
Expects a temporal item which defines a tempo curve Fenv in a info-tag 'globaltempo', and returns a list of MIDI tempo events. Returns nil in case MyItem defines no tempo curve. The tempo fenv values are in beats per minute. Due to restrictions of the MIDI protocoll, only a single global tempo is supported (note that sequencers may restrict the import of such data in a MIDI files). If multiple tempi are defined "in parallel" or nested, then "conflicting" MIDI tempo events are output.
Example: globaltempo({Fenv.linearFenv [[0.0 30.0] [1.0 240.0]]})

Args:
ccsPerSecond: how many tempo events are created per second (a float).
track: MIDI track to output, default 2 (suitable for more cases)

Time shift fenvs affect the start and end of the tempo events, but not their "spacing".


proc{RenderAndPlayMidiFile MyScore Args}
This procedure is like Out.midi.renderAndPlayMidiFile, but it additional supports continuous controllers and a global tempo curve, expressed in the score by fenvs.

Supported score format:

The info-tag 'channel', given to a temporal item, sets the MIDI channel for this item and all contained items. Example: channel(0). If a channel is defined multiple times, then a setting in a lower hierarchical level overwrites higher-level settings.

The info-tag 'program', given to a temporal item, results in a program change message with the specified program number at the beginning of the item. Example. program(64). Many instruments number patches from 1 to 128 rather than the 0 to 127 used within MIDI files. When interpreting ProgramNum values, note that they may be one less than the patch numbers given in an instrument's documentation.

The info-tag 'fenvs', given to a note or temporal container, specifies a tuple of continuous controllers for the duration this item. Each Fenv spec is a pair Controller#Fenv, where Controller is defined as for Fenv.fenvToMidiCC. Example: (cc#1)#MyFenv. Fenvs directly specify the controller values (e.g., if Controller is pitchBend, then the Fenv range is 0.0 to 16383.0, and the value 8192.0 means no pitchbend). Note that for any controller only a single Fenv should be defined at any time (otherwise they conflict with each other).

The info-tag 'timeshift', given to a temporal container, specifies a time shift function (a fenv). Example: timeshift(MyTimeshiftFenv). Time shift values are specified as time value offsets in the present timeUnit. For example, if a note has the start time 42 and its container specifies a time shift fenv with the y-value -1.0 corresponding to the start time of this note, then the MIDI note on happens at time 41. Hierarchical nesting of time shift functions is supported: if in the example above this note is recursively contained in other containers which also specify a time shift fenv, then their y-values for the note are added to the note's start time as well. Time shift fenvs also affect the timing of CC fenvs.

The info-tag 'globaltempo', given to a temporal container, specifies a tempo curve (a fenv) and is output as MIDI tempo events. Example: globaltempo(MyTempoFenv). Tempo values are specified in BPM. Due to restrictions of the MIDI protocoll, only a single global tempo is supported (note that sequencers may restrict the import of such data in a MIDI files). If multiple tempi are defined "in parallel" or nested, then "conflicting" MIDI tempo events are output.

All arguments of Out.midi.renderAndPlayMidiFile are supported. RenderAndPlayMidiFile is defined by calling Out.midi.renderAndPlayMidiFile with special clauses (namely for the tests isNote, and Score.isTemporalContainer). Clauses given to RenderAndPlayMidiFile are again appended at the beginning of the list of clauses (and so potentially overwrite the clauses defined by this procedure).

Additional arguments.
ccsPerSecond: how many continuous controller events are created per second for every Fenv (the spacing of CC events may be affected).

NOTE: timing/spacing of continuous controller events and tempo curves etc. are _not_ affected by timeshift fenvs (but their start and end are).


End