AnimJ: Difference between revisions

From Resonite Wiki
Zandario (talk | contribs)
m LogiX -> ProtoFlux and Neos -> Resonite
 
No edit summary
 
(5 intermediate revisions by 4 users not shown)
Line 1: Line 1:
<!--T:1-->
<!--T:1-->
AnimJ is a custom import format for Animation asset files in Resonite. It allows you to build arbitrary animation tracks as external JSON files and import them as native Animation assets. Those can be used to drive values with Animator or samples with [[ProtoFlux Node|ProtoFlux nodes]].
'''AnimJ''' is a custom [[Importing Assets|import format]] for Animation [[Asset|asset]] files in [[Resonite]]. It allows you to build arbitrary animation tracks as external JSON files and import them as native Animation assets. Those can be used to drive values with the Animator component, or sample values with [[:Category:ProtoFlux|ProtoFlux nodes]].


<!--T:22-->
The file extension used to import AnimJ files in to Resonite is <code>.animj</code>, however, the text inside these files is standard JSON.
Some more technical, incomplete information on AnimJ can also be found here: [[User:Lexevo/Findings/AnimJ]]


==Schema== <!--T:2-->
==Basic Overview==
Each animation can have one or more animation tracks. Resonite supports different animation track types for different purposes. Each animation track can also use any [[Resonite Primitive]] as its element. Each animation track has one or more keyframes that represent a sequence of values.
Since the AnimJ file is a representation of an Animation asset in Resonite, the file needs to have most of what the Animation asset uses to animate values. These include:
 
* Name
* Animation "tracks"
* Track type
* Track "node" and "property" (these can be anything, but are meant to be what the animation track is controlling)
* Track value type (what type of data you want to animate)
* Track "keyframes"
* Data for each keyframe
 
Please see the examples for how an animj file is structured, and take a look at the schema for more in-depth documentation.
 
==Schema==
<!--T:2-->
This schema is a more in-depth, technical look on how AnimJ files are structured.
 
Each animation can have one or more animation tracks. Resonite supports different animation track types for different purposes. Each animation track has one or more keyframes that represent a sequence of values. These are the types an animation can use:
<div style="column-count:3">
* bool
* bool2
* bool3
* bool4
* byte
* ushort
* ulong
* sbyte
* short
* int
* int2
* int3
* int4
* uint
* uint2
* uint3
* uint4
* long
* long2
* long3
* long4
* float
* float2
* float3
* float4
* floatQ
* float2x2
* float3x3
* float4x4
* double
* double2
* double3
* double4
* doubleQ
* double2x2
* double3x3
* double4x4
* color
* color32 (Not sure about this)
* string
</div>
 
NOT Supported:
* colorX
* char
* DateTime
* TimeSpan
* decimal
* enums
These are not supported due to them not being contained in the <code>Elements.Core.AnimX.elementTypes</code> list in Resonite's codebase.
 
===Value Examples===
{| class="wikitable" style="margin:auto"
|-
!Data Type!!Example
|-
|bool||<code>True</code>
|-
|bool2||<code>{"x": True, "y": False}</code>
|-
|bool3||<code>{"x": True, "y": False, "z": True}</code>
|-
|bool4||<code>{"x": True, "y": False, "z": True, "w": False}</code>
|-
|byte||<code>23</code>
|-
|ushort||<code>1200</code>
|-
|ulong||<code>2349587120938</code>
|-
|sbyte||<code>-3</code>
|-
|short||<code>-56</code>
|-
|int||<code>69</code>
|-
|int2||<code>{"x": 12, "y": -4}</code>
|-
|int3||<code>{"x": 12, "y": -4, "z": 203}</code>
|-
|int4||<code>{"x": 12, "y": -4, "z": 203, "w": 0}</code>
|-
|uint||<code>9</code>
|-
|uint2||<code>{"x": 9, "y": 4}</code>
|-
|uint3||<code>{"x": 9, "y": 4, "z": 2147483699}</code>
|-
|uint4||<code>{"x": 9, "y": 4, "z": 2147483699, "w": 1}</code>
|-
|long||<code>9876543210</code>
|-
|long2||<code>{"x": 12, "y": -4}</code>
|-
|long3||<code>{"x": 12, "y": -4, "z": 203}</code>
|-
|long4||<code>{"x": 12, "y": -4, "z": 203, "w": 0}</code>
|-
|float||<code>4.3</code>
|-
|float2||<code>{"x": 4.3, "y": 1.34}</code>
|-
|float3||<code>{"x": 4.3, "y": 1.34, "z": 2.333E+9}</code>
|-
|float4||<code>{"x": 4.3, "y": 1.34, "z": 2.333E+9, "w": -5.000000001}</code>
|-
|floatQ||<code>{"x": 1.889846E-07, "y": -0.5664063, "z": -1.762067E-07, "w": 0.8241262}</code>
|-
|float2x2||
|-
|float3x3||
|-
|float4x4||
|-
|double||<code>4.3</code>
|-
|double2||<code>{"x": 4.3, "y": 1.34}</code>
|-
|double3||<code>{"x": 4.3, "y": 1.34, "z": 2.333E+9}</code>
|-
|double4||<code>{"x": 4.3, "y": 1.34, "z": 2.333E+9, "w": -5.000000001}</code>
|-
|doubleQ||<code>{"x": 1.889846E-07, "y": -0.5664063, "z": -1.762067E-07, "w": 0.8241262}</code>
|-
|double2x2||
|-
|double3x3||
|-
|double4x4||
|-
|color||<code>{"r": 0.2, "g": 0, "b": 1, "a": 0.85}</code>
|-
|color32||<code>{"r": 0.2, "g": 0, "b": 1, "a": 0.85}</code>
|-
|string||<code>"Hello World!"</code>
|}
 
===Animation===
The base Animation object.
 
{| class="wikitable" style="margin:auto"
|-
!Property!!Type!!Description!!Required
|-
|<code>name</code>||String||The internal name of the whole Animation||No
|-
|<code>globalDuration</code>||Number||The amount of time in seconds the whole animation will play for (even if your keyframes end earlier)||No
|-
|<code>tracks</code>||array of AnimationTrack objects||The list of tracks that are included in the animation||No, but useless without
|}
 
===AnimationTrack===
The track(s) inside an Animation.
 
As of version 2024.7.4.1347 the following properties need to be specified in the exact order or loading will fail ([https://github.com/Yellow-Dog-Man/Resonite-Issues/issues/2447#issuecomment-2209514337]):
{| class="wikitable" style="margin:auto"
|-
!Property!!Type!!Description!!Required
|-
|<code>trackType</code>||<code>"Raw"</code>, <code>"Discrete"</code>, <code>"Curve"</code> or <code>"Bezier"</code>||Used to determine how each keyframe is interpolated between frames.||Yes
|-
|<code>valueType</code>||One of the [[#Schema|valid types]] as a String||Determines what value type is used throughout the track.||Yes
|-
|<code>data</code>||The rest of the AnimationTrack object [[#Generic_Animation_Track_Data|(See below)]]||Holds the keyframe data, as well as the Node and Property properties.||Yes
|}
 
Note: The AnimationTrack object imported from the JSON isn't what actually gets stored in Resonite. Instead, a custom [https://docs.microsoft.com/en-us/dotnet/api/system.text.json.serialization.jsonconverter-1 JsonConverter] is used to read the <code>trackType</code> and <code>valuetype</code> properties, and then creates the corresponding object related to those with the <code>data</code> property. (<code>RawAnimationTrack&lt;T&gt;</code>, <code>DiscreteAnimationTrack&lt;T&gt;</code>, <code>CurveAnimationTrack&lt;T&gt;</code>)
 
Therefore, there is different keyframe data depending on what type you specify in <code>trackType</code>.
 
<span id="Generic_Animation_Track_Data">The data that applies to all track types:
{| class="wikitable" style="margin:auto"
|-
!Property!!Type!!Description!!Required
|-
|<code>node</code>||String||Designed to be used to specify the object being controlled by the animation. Can be used with the Find Animation Track Index ProtoFlux node.||No
|-
|<code>property</code>||String||Designed to be used to specify the field of the object that is being controlled by the animation. Can be used with the Find Animation Track Index ProtoFlux node.||No
|-
|<code>keyframes</code>||[[#Keyframes|See Below]]||A list of values at specific time intervals to determine what the whole track's value should be at specific times.||No
|}</span>
 
Data that applies to the Raw Animation Track Type only:
{| class="wikitable" style="margin:auto"
|-
!Property!!Type!!Description!!Required
|-
|<code>interval</code>||Number||The time it takes in seconds to linearly interpolate between each keyframe (Using this will ignore <code>globalDuration</code>)||No
|}
 
===Keyframes===
The data that contains what the animation track's value should be at specific times.
 
This is structured differently for each Animation Track Type.
 
====Raw====
This track type will set the value at equal intervals only.
The <code>Keyframes</code> property is just an array of values for the Raw type. The time between each frame is determined by the <code>interval</code>.
The Animation Track will not work as intended, however, unless the <code>interval</code> property is included.
 
====Discrete====
This track type will set the value at specified times.
These keyframes are objects
{| class="wikitable" style="margin:auto"
|-
!Property!!Type!!Description!!Required
|-
|<code>time</code>||Number||The elapsed amount of time in seconds since the start of the animation in which the accompanying <code>value</code> property should be applied.||Yes
|-
|<code>value</code>||The type set in the <code>valueType</code> property. See the [[#Value Examples|examples]] for representations of each type.||The value that will be set||Yes
|}
 
====Curve====
This track type will set the values at the specified time, and interpolate between the frames to get to those values.
These keyframes are objects
{| class="wikitable" style="margin:auto"
|-
!Property!!Type!!Description!!Required
|-
|<code>time</code>||Number||The elapsed amount of time in seconds since the start of the animation in which the accompanying <code>value</code> property should be reached through interpolation.||Yes
|-
|<code>value</code>||The type set in the <code>valueType</code> property. See the [[#Value Examples|examples]] for representations of each type.||The value that will be set||Yes
|-
|<code>interpolation</code>||<code>"Linear"</code>, <code>"Tangent"</code>, <code>"Hold"</code> or <code>"CubicBezier"</code>||See [[#Interpolation types]]||Yes
|-
|<code>leftTangent</code>||The type set in the <code>valueType</code> property. See the [[#Value Examples|examples]] for representations of each type.||See [[#Interpolation types]]||Only when <code>interpolation</code> is set to "Tangent" or "CubicBezier"
|-
|<code>rightTangent</code>||The type set in the <code>valueType</code> property. See the [[#Value Examples|examples]] for representations of each type.||See [[#Interpolation types]]||Only when <code>interpolation</code> is set to "Tangent" or "CubicBezier"
|}
 
=====Interpolation types=====
Each keyframe in the Curve animation track can have a different interpolation type. These types are as follows:
 
======Linear======
The linear interpolation type will transform the value linearly from the current keyframe's value to the next keyframe's value, between the start of the current keyframe's time and the start of the next keyframe's time.
 
======Hold======
The Hold interpolation type makes the current keyframe act like the Discrete Animation Track type, where it will stay at that position and when the next keyframe's time is reached, will immediately transform to the next keyframe's value.
 
======Tangent======
Tangent is a misrepresentation of a bezier curve algorithm, which is kept in for legacy/compatibility reasons.
 
======CubicBezier======
CubicBezier represents the [https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Cubic_B%C3%A9zier_curves cubic bezier curve alorithm], in which four points are used to create a curve of the data between two keyframes. In the example gif below, P<sub>0</sub> is the current keyframe's <code>value</code>, P<sub>1</sub> is the current keyframe's <code>rightTangent</code>, P<sub>2</sub> is the next keyframe's <code>leftTangent</code> and P<sub>3</sub> is the next keyframe's <code>value</code>. The black dot would be a representation of the current value between two keyframes. This also means you do not need a <code>leftTangent</code> for your first keyframe nor a <code>rightTangent</code> for your last keyframe.
 
[[File:Bézier_3_big.gif]]


<!--T:3-->
<!--T:3-->
Line 13: Line 275:


  <!--T:4-->
  <!--T:4-->
{
 
{  
     "name": "My Animation",
     "name": "My Animation",
     "globalDuration": 0,
     "globalDuration": 0,
Line 47: Line 310:


  <!--T:17-->
  <!--T:17-->
{
{
     "name": "Test Animation Data 1",
     "name": "Test Animation Data 1",
     "globalDuration": 0,
     "globalDuration": 0,
Line 92: Line 355:


  <!--T:11-->
  <!--T:11-->
{
{
             "trackType": "Raw",
             "trackType": "Raw",
             "valueType": "float",
             "valueType": "float",
Line 111: Line 374:


  <!--T:13-->
  <!--T:13-->
{
{
             "trackType": "Discrete",
             "trackType": "Discrete",
             "valueType": "float",
             "valueType": "float",
Line 144: Line 407:


  <!--T:19-->
  <!--T:19-->
{
{
     "name": "Universe Timing (Czech)",
     "name": "Universe Timing (Czech)",
     "tracks": [
     "tracks": [
Line 240: Line 503:


  <!--T:21-->
  <!--T:21-->
{
{  
     "time": 10,
     "time": 10,
     "value": 0,
     "value": 0,

Latest revision as of 23:13, 4 July 2024

AnimJ is a custom import format for Animation asset files in Resonite. It allows you to build arbitrary animation tracks as external JSON files and import them as native Animation assets. Those can be used to drive values with the Animator component, or sample values with ProtoFlux nodes.

The file extension used to import AnimJ files in to Resonite is .animj, however, the text inside these files is standard JSON.

Basic Overview

Since the AnimJ file is a representation of an Animation asset in Resonite, the file needs to have most of what the Animation asset uses to animate values. These include:

  • Name
  • Animation "tracks"
  • Track type
  • Track "node" and "property" (these can be anything, but are meant to be what the animation track is controlling)
  • Track value type (what type of data you want to animate)
  • Track "keyframes"
  • Data for each keyframe

Please see the examples for how an animj file is structured, and take a look at the schema for more in-depth documentation.

Schema

This schema is a more in-depth, technical look on how AnimJ files are structured.

Each animation can have one or more animation tracks. Resonite supports different animation track types for different purposes. Each animation track has one or more keyframes that represent a sequence of values. These are the types an animation can use:

  • bool
  • bool2
  • bool3
  • bool4
  • byte
  • ushort
  • ulong
  • sbyte
  • short
  • int
  • int2
  • int3
  • int4
  • uint
  • uint2
  • uint3
  • uint4
  • long
  • long2
  • long3
  • long4
  • float
  • float2
  • float3
  • float4
  • floatQ
  • float2x2
  • float3x3
  • float4x4
  • double
  • double2
  • double3
  • double4
  • doubleQ
  • double2x2
  • double3x3
  • double4x4
  • color
  • color32 (Not sure about this)
  • string

NOT Supported:

  • colorX
  • char
  • DateTime
  • TimeSpan
  • decimal
  • enums

These are not supported due to them not being contained in the Elements.Core.AnimX.elementTypes list in Resonite's codebase.

Value Examples

Data Type Example
bool True
bool2 {"x": True, "y": False}
bool3 {"x": True, "y": False, "z": True}
bool4 {"x": True, "y": False, "z": True, "w": False}
byte 23
ushort 1200
ulong 2349587120938
sbyte -3
short -56
int 69
int2 {"x": 12, "y": -4}
int3 {"x": 12, "y": -4, "z": 203}
int4 {"x": 12, "y": -4, "z": 203, "w": 0}
uint 9
uint2 {"x": 9, "y": 4}
uint3 {"x": 9, "y": 4, "z": 2147483699}
uint4 {"x": 9, "y": 4, "z": 2147483699, "w": 1}
long 9876543210
long2 {"x": 12, "y": -4}
long3 {"x": 12, "y": -4, "z": 203}
long4 {"x": 12, "y": -4, "z": 203, "w": 0}
float 4.3
float2 {"x": 4.3, "y": 1.34}
float3 {"x": 4.3, "y": 1.34, "z": 2.333E+9}
float4 {"x": 4.3, "y": 1.34, "z": 2.333E+9, "w": -5.000000001}
floatQ {"x": 1.889846E-07, "y": -0.5664063, "z": -1.762067E-07, "w": 0.8241262}
float2x2
float3x3
float4x4
double 4.3
double2 {"x": 4.3, "y": 1.34}
double3 {"x": 4.3, "y": 1.34, "z": 2.333E+9}
double4 {"x": 4.3, "y": 1.34, "z": 2.333E+9, "w": -5.000000001}
doubleQ {"x": 1.889846E-07, "y": -0.5664063, "z": -1.762067E-07, "w": 0.8241262}
double2x2
double3x3
double4x4
color {"r": 0.2, "g": 0, "b": 1, "a": 0.85}
color32 {"r": 0.2, "g": 0, "b": 1, "a": 0.85}
string "Hello World!"

Animation

The base Animation object.

Property Type Description Required
name String The internal name of the whole Animation No
globalDuration Number The amount of time in seconds the whole animation will play for (even if your keyframes end earlier) No
tracks array of AnimationTrack objects The list of tracks that are included in the animation No, but useless without

AnimationTrack

The track(s) inside an Animation.

As of version 2024.7.4.1347 the following properties need to be specified in the exact order or loading will fail ([1]):

Property Type Description Required
trackType "Raw", "Discrete", "Curve" or "Bezier" Used to determine how each keyframe is interpolated between frames. Yes
valueType One of the valid types as a String Determines what value type is used throughout the track. Yes
data The rest of the AnimationTrack object (See below) Holds the keyframe data, as well as the Node and Property properties. Yes

Note: The AnimationTrack object imported from the JSON isn't what actually gets stored in Resonite. Instead, a custom JsonConverter is used to read the trackType and valuetype properties, and then creates the corresponding object related to those with the data property. (RawAnimationTrack<T>, DiscreteAnimationTrack<T>, CurveAnimationTrack<T>)

Therefore, there is different keyframe data depending on what type you specify in trackType.

The data that applies to all track types:

Property Type Description Required
node String Designed to be used to specify the object being controlled by the animation. Can be used with the Find Animation Track Index ProtoFlux node. No
property String Designed to be used to specify the field of the object that is being controlled by the animation. Can be used with the Find Animation Track Index ProtoFlux node. No
keyframes See Below A list of values at specific time intervals to determine what the whole track's value should be at specific times. No

Data that applies to the Raw Animation Track Type only:

Property Type Description Required
interval Number The time it takes in seconds to linearly interpolate between each keyframe (Using this will ignore globalDuration) No

Keyframes

The data that contains what the animation track's value should be at specific times.

This is structured differently for each Animation Track Type.

Raw

This track type will set the value at equal intervals only. The Keyframes property is just an array of values for the Raw type. The time between each frame is determined by the interval. The Animation Track will not work as intended, however, unless the interval property is included.

Discrete

This track type will set the value at specified times. These keyframes are objects

Property Type Description Required
time Number The elapsed amount of time in seconds since the start of the animation in which the accompanying value property should be applied. Yes
value The type set in the valueType property. See the examples for representations of each type. The value that will be set Yes

Curve

This track type will set the values at the specified time, and interpolate between the frames to get to those values. These keyframes are objects

Property Type Description Required
time Number The elapsed amount of time in seconds since the start of the animation in which the accompanying value property should be reached through interpolation. Yes
value The type set in the valueType property. See the examples for representations of each type. The value that will be set Yes
interpolation "Linear", "Tangent", "Hold" or "CubicBezier" See #Interpolation types Yes
leftTangent The type set in the valueType property. See the examples for representations of each type. See #Interpolation types Only when interpolation is set to "Tangent" or "CubicBezier"
rightTangent The type set in the valueType property. See the examples for representations of each type. See #Interpolation types Only when interpolation is set to "Tangent" or "CubicBezier"
Interpolation types

Each keyframe in the Curve animation track can have a different interpolation type. These types are as follows:

Linear

The linear interpolation type will transform the value linearly from the current keyframe's value to the next keyframe's value, between the start of the current keyframe's time and the start of the next keyframe's time.

Hold

The Hold interpolation type makes the current keyframe act like the Discrete Animation Track type, where it will stay at that position and when the next keyframe's time is reached, will immediately transform to the next keyframe's value.

Tangent

Tangent is a misrepresentation of a bezier curve algorithm, which is kept in for legacy/compatibility reasons.

CubicBezier

CubicBezier represents the cubic bezier curve alorithm, in which four points are used to create a curve of the data between two keyframes. In the example gif below, P0 is the current keyframe's value, P1 is the current keyframe's rightTangent, P2 is the next keyframe's leftTangent and P3 is the next keyframe's value. The black dot would be a representation of the current value between two keyframes. This also means you do not need a leftTangent for your first keyframe nor a rightTangent for your last keyframe.

The typical structure of animation track is following:


{   
   "name": "My Animation",
   "globalDuration": 0,
   "tracks": [
       {
           "trackType": "Discrete",
           "valueType": "float",
           "data": {
               "node": "Test",
               "property": "Test",
               "keyframes": [
                   {
                       "time": 0,
                       "value": 1
                   },
                   {
                       "time": 1,
                       "value": 42
                   },
                   {
                       "time": 5,
                       "value": 20
                   }
               ]
           }
       }
   ]
}


follows is a example track with a float3 type:

{
   "name": "Test Animation Data 1",
   "globalDuration": 0,
   "tracks": [
       {
           "trackType": "Discrete",
           "valueType": "float3",
           "data": {
               "node": "TestData",
               "property": "TestData",
               "keyframes": [
                   {
                       "time": 0,
                       "value": {
                           "x": 1.0,
                           "y": 2.0,
                           "z": 3.0
                       }
                   }
               ]
           }
       }
   ]
}

Animation Track

Animation track is a single timeline of values. You can have as many animation tracks in an Animation as you like. Each track can be uniquely identified by Node and Property.

Node indicates which object in the hierarchy given track controls, while Property specifies which of its fields will be controlled when the animation is bound to a scene objects. For example Node "Fan Blade" and Property "Rotation" for track of type floatQ can control rotation animation.

However both are just names and do not need to correspond to anything in the scene. You can ignore the names and refer to the animation track by its index or use the names for a dynamic lookup with ProtoFlux.

Animation Track Types

Resonite currently supports following animation track types. Each type is optimized for different use-case. Animation can mix tracks of different types.

Raw Animation Track

This is the simplest form of animation. It is a raw sequence of values, with a global Interval (length of the animation). The keyframes are uniformly distributed in this track. This can be ideal representation for baked animations with a regular framerate.


Here's example of such track:

{
           "trackType": "Raw",
           "valueType": "float",
           "data": {
               "node": "Test",
               "property": "Test",
               "keyframes": [
                  0.5,
                  0.7,
                  0.8,
                  0.9
               ]
           }
}

Discrete Animation Track

This track is useful for keyframes that are irregular and do not need interpolation. Each value is held for the entire duration of the keyframe, until the next one comes in. This is the type of animation track that Resonite imports subtitles as, but it can be used for any value. It can be useful also in cases you do your own custom interpolation (e.g. with Smooth Lerp) and just need to change the "master" value.

{
           "trackType": "Discrete",
           "valueType": "float",
           "data": {
               "keyframes": [
                   {
                       "time": 0,
                       "value": 2
                   },
                   {
                       "time": 1,
                       "value": 8
                   },
                   {
                       "time": 5,
                       "value": 20
                   }
               ],
               "node": "Test",
               "property": "Test"
           }
}

Curve Animation Track

This is the most versatile (and typical) type of animation track, used for values that are interpolated between. Each keyframe can use different types of interpolation (Hold, Linear and Tangent). The Tangent interpolation keyframes specify the Left and Right tangents, which indicate how the keyframe's base value changes over time as it transitions to the next keyframe.

When importing 3D Model files, this type of animation track is used.

The following example shows the Curve animation track with linear interpolation between keyframes. It also has additional Discrete animation track.

{
   "name": "Universe Timing (Czech)",
   "tracks": [
       {
           "trackType": "Curve",
           "valueType": "float",
           "data": {
               "node": "Scale",
               "property": "",
               "keyframes": [
                   {
                       "time": 0,
                       "value": -17,
                       "interpolation" : "Linear"
                   },
                   {
                       "time": 49.97,
                       "value": -17,
                       "interpolation" : "Linear"
                   },
                   {
                       "time": 97,
                       "value": -5,
                       "interpolation" : "Linear"
                   },
                   {
                       "time": 128,
                       "value": 0,
                       "interpolation" : "Linear"
                   },
                   {
                       "time": 134,
                       "value": 0,
                       "interpolation" : "Linear"
                   },
                   {
                       "time": 152,
                       "value": 5.5,
                       "interpolation" : "Linear"
                   },
                   {
                       "time": 175,
                       "value": 7,
                       "interpolation" : "Linear"
                   },
                   {
                       "time": 184,
                       "value": 7.5,
                       "interpolation" : "Linear"
                   },
                   {
                       "time": 207,
                       "value": 12,
                       "interpolation" : "Linear"
                   },
                   {
                       "time": 247,
                       "value": 27,
                       "interpolation" : "Linear"
                   }
               ]
           }
       },
       {
           "trackType": "Discrete",
           "valueType": "int",
           "data": {
               "node": "Phase",
               "property": "",
               "keyframes": [
                   {
                       "time": 0,
                       "value": 0
                   },
                   {
                       "time": 45,
                       "value": 1
                   },
                   {
                       "time": 49.97,
                       "value": 2
                   },
                   {
                       "time": 247,
                       "value": 3
                   }
               ]
           }
       }
   ]
}

The schema for keyframes with tangents (these define a bezier curve) is following:

{   
   "time": 10,
   "value": 0,
   "leftTangent" : -10,
   "rightTangent" : 5,
   "interpolation" : "Tangent"
}