format_version = "1.0"
product_id = "se.propellerheads.Parsec"
version_number = "1.0.1f4"
manufacturer = "Reason Studios"
long_name = "Parsec Spectral Synthesizer"
medium_name = "Parsec"
short_name = "Parsec"
device_type = "instrument"
accepts_notes = true
supports_patches = true
default_patch = "/Public/Glistening.repatch"
auto_create_track = true
auto_create_note_lane = true
supports_performance_automation = true
device_height_ru = 9
automation_highlight_color = {r = 60, g = 255, b = 2}
Introduction
Jukebox is a technology from Reason Studios for developing portable, robust and long-lived software instruments and effects. It is a central part of the Reason Studios Rack Extensions system, which makes it possible for Reason Studios and third parties to create and distribute devices for the Reason Studios Reason rack.
This document describes the main concepts of the Jukebox technology and how the different Jukebox APIs and subsystems work together on a higher level. For more detailed information, refer to the Jukebox Scripting Specification document. To get an introductory overview of the Rack Extensions system, read the "Rack Extension Developer’s Guide".
It is expected that readers of this document are C/C++ developers, familiar with digital signal processing and realtime concepts. Basic knowledge about the Lua scripting language can help. It is also expected that the reader knows about general music application concepts such as note numbers, velocity and automation. Knowing about more Reason - specific concepts such as control voltages (CV), auto-routing or Remote™ is really helpful as well.
Design goals
Jukebox has been designed with focus on portability, robustness and longevity. We want any implementation based on Jukebox technology to be able to run on all platforms that Reason Studios currently supports, but also on future platforms that we are not running on today.
It should be hard to make mistakes that bring the whole host down, and if bad things do happen, the end user’s working document should be kept safe.
Jukebox isolates implementers from operating system and other platform issues and makes it the responsibility of the host to keep up with system updates and changing runtime dependencies.
Main concepts
A device in the Jukebox world is called a "45". 45’s have a number of audio inputs and audio outputs, a piece of C++ code that performs some kind of signal processing and a set of properties that control how this processing is performed. To be able to tweak and visualize the current value of properties, there is a GUI system that ties user gestures and graphical representations to properties in a portable manner.
A Jukebox 45 can be divided into three major parts, from user end to the actual signal processing: GUI, data model and processing code.
The different design goals described earlier put demands on the feature set of each of these parts, but also limitations in terms of flexibility.
Jukebox 45’s are hosted in a sandboxed environment that provides much of the functionality needed to get a fully functional 45. A data driven approach has been used for major parts of the data model and GUI, and it is the responsibility of the host to provide robust handling of that data and to maintain platform compatibility.
The native code that makes up the realtime part runs in a sandboxed environment, which is both limited in terms of functionality and keeps 45’s under close supervision. There is for example no direct access to OS libraries, and a 45 that tries to allocate memory while rendering audio will be shut down.
A special version of Reason called Reason Recon is provided with the Jukebox SDK for testing and debugging purposes.
Motherboard
The data model in Jukebox is called the Motherboard and is defined using the Lua scripting language. The Motherboard holds properties that are organized into property set objects. Some of the objects are built-in and defined by the host; others are defined specifically for each 45 implementation. 45-specific properties are known as custom properties.
The Motherboard also contains information about other things, such as input and output sockets, MIDI CC and Remote™ mappings, and automatic routing hints.
Once defined, the Motherboard is under complete control of the host, even though some parts can be customized by the use of Lua scripts. Because of this, the host can provide complete undo handling, generic patch handling and so on.
GUI
The Jukebox design goals put special demands on the GUI side. A Jukebox GUI must have:
Scalable graphics that works from lower resolution small displays to higher resolution displays of tomorrow.
Pixel-unaware drawing and user actions. In fact, there can be no assumptions made on any kind of input method. That is all handled by the host.
In Jukebox, a GUI is defined as a set of high resolution 2D images that are connected to the motherboard via Lua scripts. The panels and parts that make up a device are represented by 2D images.
Processing code
For performance reasons, data processing is done in native code, but must still live up to the Jukebox design goals. All data processing in Jukebox is done in portable C++ by implementing two functions:
JBox_Export_RenderRealtime()
(which is responsible for processing audio), andJBox_Export_CreateNativeObject()
(which is primarily responsible for allocating memory).
The C++ code runs in a limited environment with no access to operating system functions or external libraries other than those compiled from source code or provided by Jukebox.
Realtime
The processing side of Jukebox is often referred to as the Realtime, even though not all processing is done in real time, as we will see later.
Realtime Controller
To keep the Realtime as simple as possible, the host is responsible for much of the housekeeping. That part of the host is called the Realtime Controller (RTC). For situations where more control is needed, it is possible to extend the built-in Realtime Controller behavior by the use of Lua scripts.
The virtual fuse
If a 45 misbehaves or generates an exception or error, the host will halt the 45 and stop sending information to it. This is referred to as blowing the fuse. After the fuse has blown the 45 is "dead" in the sense that it cannot process sound and cannot be modified by the user. However, the user’s song document can still be manipulated and saved.
Anatomy of a 45
All 45 projects contain the following:
Lua scripts:
info.lua
(contains general settings)motherboard_def.lua
(motherboard definition)realtime_controller.lua
(realtime controller)hdgui_2D.lua
(GUI widget definitions)display.lua
(code that draws into custom displays)
C++ source files (realtime DSP processing)
Localized text resources
GUI resources and metadata files
For the 2D GUI pipeline, the 45 project contains (see 2D GUI Designer Manual for details):
Static images
Film strip images
Graphics definitions file (
device_2D.lua
)
A 45 may also contain any number of
Binary resources (Binary Large Objects)
Samples
Patches (device patches and/or Combinator patches)
Important! |
All non-binary files, including all Lua scripts, must be encoded as ASCII or as UTF-8 Unicode without BOM. |
Resources
A Jukebox 45 can include any number of files that are either private
to the 45 or publically available to other devices or subsystems in
the same host. An example of a private resource could be a wavetable
that will be loaded as a BLOB (Binary Large Object) and interpreted by
45 specific code. There is no need for that resource to be exposed to
other devices. Obvious examples of public resources are the patches
that come with a 45. These must be publically available, as the host
will expose them for browsing, etc. Public resources live in the Resources/Public
resource subfolder of a Jukebox project and private in the
Resources/Private
resource subfolder. Assets that do not live
in any of these folders are internal to the Jukebox system and will only
be accessed by the host. When referring to resources in scripts, a forward
slash separated resource path is used.
Important! |
The Public subfolder will be the root of the
publically browsable file system as viewed by for example the
Reason browser. Putting samples in the Public
subfolder will make them available for export and use by other devices.
For now, we recommended keeping samples private unless they are part
of the public patches for the 45, in which case they
must
be included in the Public folder (see next note). |
Important! |
If you choose to store Combinator and/or other kinds of patches in
the Public folder, it is very important that any
files referenced by those patches, such as samples, are made
available to the Rack Extension by including them in the
Public
folder. (Samples that are part of the Factory or Orkester sound banks
do not have to be included.) To do this, follow these steps: 1) Put
the files you want to reference in the Public
folder and rebuild the Rack Extension. 2) Create the patch in Recon.
When you point to referenced files, browse into the Rack Extension.
Do not point to a file on your hard drive!
3) Save the patch. 4) Copy the patch to the Public folder
and rebuild the Rack Extension. |
Localized text resources
With a few exceptions, all strings that are shown in the GUI of a 45 must be localized, including property names and values. In situations where you refer to a localized string, you provide a UI text key rather than the string itself. The host uses the key to fetch the appropriate string for the currently selected language from the 45’s set of localized text databases. There is one database for each language; English is required. The other languages are German, French, and Japanese. See the Jukebox Scripting Specification for information about the format and location of the database files.
General configuration
The info.lua
file is the main descriptor of a 45. It is used
to uniquely identify the 45, and also contains host-specific configuration
settings.
Example:
The format_version
entry specifies which version of the
info.lua
format that the file follows. All files in Jukebox (except C++ files) have
an individual format version. As the Jukebox API is extended or updated,
the format of the files may change. The Jukebox Scripting Specification document
contains detailed information about all Jukebox configuration and script
file formats, including which features are available in which file format
version.
The product_id
is a unique identifier for the product and may
not change once the product has been uploaded to the Reason Studios servers.
It is never shown in host GUIs.
The version_number
specifies the product version of the 45.
It must be unique (once you have uploaded a universal 45 with a certain version
number you cannot upload the same version number again). The version number
is shown to end-users in the Reason Studios Shop. The number has the following
format:
A.B.Cr
where A
is the major number, B
is the
minor number, C
is the
revision number, and r
is a letter that indicates
the stage of the 45 (d
for testing/debugging,
b
for beta, and f
for final/release candidates). The stage is
not shown in the Reason Studios Shop. It is recommended to follow the following
conventions:
Update the major number to indicate paid upgrades.
Update the minor number to indicate feature additions.
Update the revision number to indicate bug fixes or minor updates.
The manufacturer
string contains the name of the manufacturer.
It is shown in the Shop and in the host.
The long_name
string contains the name of the device. It is
shown in the Shop and in the host.
The medium_name
string contains a shorter version of the device
name. It is used when the host needs to identify the device but has limited
space to do so. For example, Reason uses the medium name in the in the Undo
menu ("Undo create Synthesizer").
The short_name
string contains a short version of the device
name. It is used in situations where the host has very little space to display
the device name. For example, Reason uses the short name when auto-naming
new instances of the device in the Rack view.
The automation_highlight_color
entry specifies the color of
automation indicators in Reason. It should not be changed unless absolutely
necessary.
The device_height_ru
entry contains the device height in Rack
Units. The value must match the panel graphics in the GUI.
The default_patch
entry contains the name of the patch to load
by default, if the device supports patches (see below).
The supports_patches
flag specifies whether the device is able
to load/save patches or not.
The device_type
specifies whether the 45 is an instrument, effect,
or utility device. The setting is used to configure certain Jukebox functionality
(such as automatic routing) but may also be used by the host to organize
products into different categories.
The other parameters in the example above are Reason-specific configuration parameters.
See the Jukebox Scripting Specification for detailed information about
the format of info.lua
.
Patches
All Jukebox devices share a common patch format, which is a simple
XML-based format. The file extension for patch files is .repatch
. See the Jukebox Scripting Specification document for details.
The supports_patches
boolean setting in
info.lua
controls whether a 45 uses patches or not. To allow the user to browse
for patches, add a patch_browse_group
and a
patch_name
GUI widget pair to the front and folded front panels.
See the GUI Designer Manual and the Jukebox Scripting Specification for
details.
It is not necessary for a 45 to support patches. If it does, however,
the Public
resources folder must contain a default patch
(which is specified in info.lua
). Creating a default
patch manually (by editing an XML file) is not recommended. Instead,
set default_patch
in info.lua
to any patch and create an instance of the 45 in Reason. Reason will fail
to load the patch (and show an error message) but will not blow the fuse.
Since your 45 is now running, it is now straightforward to create a patch,
save it to the Public
resources folder, and point to it
in info.lua
.
If you you wish to create patches that use references to samples you will supply, you need to include the samples in your Public folder and build the 45 first. After building the 45, load the samples from the devices public folder by navigating to Rack Extensions ⇒ 45 in the Reason/Recon browser in order to get the correct references in the patch. The same goes for building combi-patches that reference samples that should be included in your 45.
Devices that support patches must include a reasonable number of well organized patches.
Motherboard definition
The motherboard is where the data model and much of the logic of a 45
lives. This is also the part that provides a lot of the core
functionality of the Jukebox environment. The motherboard is defined in
the motherboard_def.lua
file.
The most important part of the motherboard is the motherboard object model
(MOM). It contains the properties that the host uses to communicate with
the 45 (and vice versa). Part of the MOM is fixed and provided by Jukebox,
and includes properties containing the current sample rate and song position.
The other properties in the MOM are
custom properties
and are defined by the 45 in the motherboard_def.lua
file. All
custom properties are automatically hooked into the host environment, which
enables features such as undo, loading and saving patches.
The motherboard definition also contains audio and CV inputs and outputs, Remote™ and MIDI CC configurations, and hints to the host about how the device should be routed to other Jukebox devices.
It can optionally also define user samples, allowing the use of audio sample files selected by the end user. See section User Samples for more.
Example:
format_version = "2.0"
--[[ Motherboard Object Model (MOM) configuration ]]--
-- Declaration of a motherboard property set object
custom_properties = jbox.property_set{
-- Properties in the document owner scope
document_owner = {
properties = {
-- Property declaration (number type)
volume = jbox.number{
property_tag = 42,
default = 0.7,
ui_name = jbox.ui_text("Volume_UI"),
ui_type = jbox.ui_percent{},
},
-- Performance properties
modWheel = jbox.performance_modwheel{},
pitchBend = jbox.performance_pitchbend{},
},
},
-- Properties in the Realtime Controller owner scope
rtc_owner = {
properties = {
-- Native object to hold C++ state for the 45 instance
instance = jbox.native_object{ },
},
},
-- Properties in the Realtime owner scope
rt_owner = {
properties = {
-- Property declaration (boolean type)
note_on = jbox.boolean{
default = false,
ui_name = jbox.ui_text("NoteOn_UI"),
ui_type = jbox.ui_linear{min=0, max=1, units={{decimals=0}}},
},
},
},
}
--[[ MIDI configuration ]]--
midi_implementation_chart = {
-- Mappings between MIDI CC and properties in the document owner scope
midi_cc_chart = {
[12] = "/custom_properties/volume",
},
}
--[[ Remote(TM) configuration ]]--
remote_implementation_chart = {
["/custom_properties/volume"] = {
internal_name = "Volume",
short_ui_name = jbox.ui_text("Volume_Short"),
shortest_ui_name = jbox.ui_text("Volume_Shortest")
},
}
--[[ Property grouping for host's GUI ]]--
ui_groups = {
{
ui_name = jbox.ui_text("Group_Volume"),
properties = {
"/custom_properties/volume",
},
},
}
--[[ Input and output sockets ]]--
-- CV input sockets
cv_inputs = {
note_cv = jbox.cv_input{
ui_name = jbox.ui_text("CV_In_Note")
},
gate_cv = jbox.cv_input{
ui_name = jbox.ui_text("CV_In_Gate")
},
}
-- Audio output sockets
audio_outputs = {
left = jbox.audio_output{
ui_name = jbox.ui_text("Audio_Out_Left")
},
right = jbox.audio_output{
ui_name = jbox.ui_text("Audio_Out_Right")
},
}
--[[ (optional) User Samples ]]--
user_samples = {
jbox.user_sample{
ui_name = jbox.ui_text("text_Sample0_UIName"),
},
}
--[[ Automatic routing configuration ]]--
-- The 45 should be routed as a stereo instrument
jbox.add_stereo_instrument_routing_hint{
left_output = "/audio_outputs/left",
right_output = "/audio_outputs/right"
}
-- Sockets on the back panel of the 45 that the host can auto-route to
jbox.add_stereo_audio_routing_target{
signal_type = "normal",
left = "/audio_outputs/left",
right = "/audio_outputs/right",
auto_route_enable = true
}
jbox.add_cv_routing_target{
signal_type = "gate",
path = "/cv_inputs/gate_cv",
auto_route_enable = true
}
jbox.add_cv_routing_target{
signal_type = "pitch",
path = "/cv_inputs/note_cv",
auto_route_enable = true
}
-- Let the host know that these two sockets form a audio stereo L+R pair
jbox.add_stereo_audio_routing_pair{
left = "/audio_outputs/left",
right = "/audio_outputs/right"
}
After the format_version
specification, this example
declares a
property set object with the name
custom_properties
. A property set object is, as the name
suggests, a container for properties. All properties are named, and the
name must be unique inside the property set object.
Important! |
Property names must contain ASCII characters only. A property name cannot be longer than 34 characters. |
The /custom_properties
property set object (that holds "your"
properties) is not the only property set in the Motherboard of a 45. The
host automatically adds other property sets for, e.g., receiving note states
or keeping track of sequencer state. These are described in details in the
Jukebox Scripting Specification.
A property is referred to by its property set path. For
example, the property named volume
in the
custom_properties
object would be referred to as
/custom_properties/volume
In Lua, the functions jbox.load_property()
and
jbox.store_property()
are used to read from and write to properties.
In C++, you use the JBox_GetMotherboardObjectRef()
function
to obtain a reference to a motherboard object, followed by
JBox_MakePropertyRef()
to obtain a reference to a property.
These references never change during the lifetime of a 45 and may be stored
in C++ data structures.
You may also assign a tag to a property. In the example above,
the property named volume
has been given the tag
42
. Property tags allow you to refer to properties via
numbers instead of via their property paths, which is useful, e.g., in
switch
statements.
Property ownership
All properties in a property set object have an owner scope. The owner scope defines which part of the 45 that is allowed to write to the property. There are four owners:
gui_owner
- Properties in this scope can be modified by the host’s GUI widgets. The properties are normally not stored in documents or patches. In Reason, GUI property changes are saved "transparently" in the undo history in the sense that all GUI property changes are "remembered" but they are not visible to the user in the Undo menu.document_owner
- Properties in this scope may be stored in documents and/or patches. The properties can be modified by the host’s GUI widgets and/or by loading a patch or song.rtc_owner
- Properties in this scope can be modified by the Realtime Controller. They can not be stored in documents or patches.rt_owner
- Properties in this scope can be modified by the Realtime. They can not be stored in documents or patches.
Tip! |
You use the persistence flag to specify whether
properties in the document owner scope should be saved to patches
and/or documents. Set this flag to "song" for Dry/Wet
knobs on effects! This will store the Dry/Wet setting in songs, but
not in patches. |
MOM property types
The Jukebox property system is strictly typed. These are the types:
Number - Double precision floating point. Can be either continuous or discrete (stepped).
Boolean - True or false.
String - Non-localized 8-bit character string.
Sample - An audio sample, stereo or mono.
BLOB - Binary Large Object that contains raw bytes. BLOBs usually contains static data that the 45 needs to compute sound, such as pre-computed waveform tables. See note on endianness below!
DSP Buffer - An array of 32-bit float samples. DSP buffers are used for sending audio data from the host to the 45’s C++ sound rendering code (and vice versa).
Native Object - Holds data returned by
JBox_Export_CreateNativeObject()
. A native object is a memory block that has been allocated dynamically by the 45 and that is used in its C++ sound rendering code. See section Realtime for details.
Important! |
All Rack Extension code must be endian safe. It is incorrect to assume that the host has any specific endianness. Reading integer data from a BLOB should only be done on a byte by byte basis (using bit shifts to create the multibyte value). You may not assume that the host’s endianness for floating point values is the same as its endianness for integers. You may not assume that the floating point representation is IEEE 754 or any other specific representation. If you need to store floats in a BLOB you can use ASCII strings, or store the exponent and mantissa separately as integers. |
When you read a value from a property in C++, the value is represented
as an opaque type called TJBox_Value
. You use toolbox
functions to access the actual contents of the
TJBox_Value
instance. Conversely, when you update the value of a property in C++, you
construct a TJBox_Value
using toolbox functions and send it to the host.
Important! |
You must never store TJBox_Value instances in C++
data structures! A TJBox_Value instance is only
guaranteed to be correct in the scope of a single call to
JBox_Export_RenderRealtime() . |
In Lua, number, boolean, and string property values are represented by the corresponding built-in Lua types. The other property types are represented by opaque types. For these types, there exist Lua functions for accessing the contents and for constructing new values.
All property types have a well-defined default value. Some property types require that you supply a default value explicitly when you create the property (see Jukebox Scripting Specification for details).
Performance properties
If the host responds to MIDI Continuous Controller messages, it may
require special handling for messages for modulation wheel, pitch
bend, sustain pedal, expression, breath control, and aftertouch.
Therefore, these messages use special-purpose property types. In the
example above, the properties named modWheel
and
pitchBend
are defined as performance properties. See the Jukebox
Scripting Specification for details.
User Samples
A device with user samples takes part in the native host features to browse for audio sample files, and create new files by sampling, bouncing or editing (sample editor). Optionally, the device can also specify to include one or more sample meta-data properties (the Sample Parameters) in its patch data. User samples also enable host properties under the /device_host object, which the device can use to integrate with the user samples hosting.
Example:
format_version = "2.0"
user_samples = {
jbox.user_sample{
ui_name = jbox.ui_text("text_Sample0_UIName"),
sample_parameters = {
"root_key",
"tune_cents",
"play_range_start",
"play_range_end",
"loop_range_start",
"loop_range_end",
"loop_mode",
"preview_volume_level"
}
},
}
This example defines properties for a user browsable sample item and all of its sample parameters. The property path of the sample item is /user_samples/0/item, and each of the sample parameters are members/fields of the same object instance (e.g. /user_samples/0/root_key). See the Jukebox Scripting Specification for details.
User interface names and types
The user interface name (ui_name
) of a property
is a localized string that contains the name of the property. It is
used by the host to identify the property in its GUI. Since the string
is localized, the
jbox.ui_text()
function is used to fetch the appropriate
string from the localized text database. In the example above, the UI
name for the
volume
property uses the "Volume_UI"
key, which
means that the localized text databases would all have entries like this:
Resources/English/texts.lua
:
format_version = "1.0"
texts = {
["Volume_UI"] = "Volume" -- English
}
Resources/German/texts.lua
:
format_version = "1.0"
texts = {
["Volume_UI"] = "Volumen" -- German
}
etc.
The user interface type (ui_type
) entry in a
property definition specifies how the value of the property should be
presented in the host’s GUI. The host may allow the user to
enter values for the property, so the user interface type may be used
for both input and output. See the Jukebox Scripting Specification for
details on how to set up UI types.
Host properties
The host will automatically populate the MOM with a collection of property set objects that it uses to expose internal state to the 45. These properties are called host properties. See the Jukebox Scripting Specification for details.
MIDI implementation
The midi_implementation_chart
section in
motherboard_def.lua
contains MIDI-related configuration settings for the 45. In the current
version of Jukebox, it has one entry, the
MIDI CC chart. This chart associates properties in the
document_owner
scope with MIDI Continuous Controllers. When
such an association is in place, the user can modify the property via MIDI
CC messages (if this is supported by the host).
Important! |
A number of CC:s are reserved by Jukebox for internal use. See the Jukebox Scripting Specification for details. |
The MIDI Implementation Chart document (which can be found in the Reason documentation folder) contains the MIDI CC associations for all legacy devices in Reason. If your 45 is similar to one of the legacy devices, we strongly recommended to use similar associations if possible.
Remote™ implementation
The Remote™ protocol is designed to simplify communication between Reason and third-party hardware keyboards and controllers. The protocol translates MIDI messages into messages that are specific to Reason. In order to integrate properly with the Remote system, the 45 must provide the Jukebox host with information about how its various controls (knobs, faders, etc.) should be mapped to Remote-enabled hardware controllers.
From the user’s perspective, a Remote-enabled hardware controller is "wired" to work with Reason: when a Reason device is selected in the rack, for example, the hardware control surface items (such as physical knobs and faders) are automatically associated with the appropriate controllers on the rack device. Status indicators on the rack device or other indicators in Reason can be mirrored on lamps and LEDs on the hardware controller (e.g., the status of the SOLO and MUTE buttons in the Reason mixer). If the hardware controller contains a display, the Remote protocol allows the hardware controller to show the values of a rack device parameter, or the location of the song position, etc.
Remote™ codecs
Each hardware controller that supports Remote must have a corresponding codec (provided by the hardware manufacturer). All Remote codecs that are available on the user’s computer are scanned by Reason on startup, which allows the user to select which controller to use in the Control Surfaces tab in Preferences. The codec contains a list of the available control surface items such as keyboard, faders, buttons, etc. The codec also contains a set of rules that are used to translate MIDI messages sent from the hardware controller to Reason (and vice versa).
Remote™ maps
The Remote codec contains information about the physical layout of a hardware controller, together with information about how MIDI messages should be sent to and from the the controller. But the codec does not contain any information about how to map specific control surface controls to rack device widgets in Reason. Such information is collected in Remote maps.
For example, the following excerpt from a Remote map for a
hypothetical hardware controller with eight knobs defines mappings
for the Malström instrument. It first sets up three groups (Filter
, Osc A
, and Osc B
), or variations, that
the user can switch between when controlling Malström. In the first
variation the eight knobs control the filters, in the second
variation the knobs control oscillator A, etc. After the group
definition statement, a list of specific mappings follow. Each
mapping associates a control surface ID (e.g., Knob 1
,
which would be defined in the codec) with a remotable item
(e.g., Filter A Freq
), and which group (Filter
, Osc A
, or Osc B
in this case) that the mapping
belongs to.
Scope Reason Studios Malstrom Graintable Synthesizer Define Group Keyboard Shortcut Variations Filter Osc A Osc B Map Knob 1 Filter A Freq Filter Map Knob 2 Filter A Resonance Filter Map Knob 3 Filter B Freq Filter Map Knob 4 Filter B Resonance Filter Map Knob 5 Filter Env Attack Filter Map Knob 6 Filter Env Decay Filter Map Knob 7 Filter Env Sustain Filter Map Knob 8 Filter Env Release Filter Map Knob 1 Oscillator A Motion Osc A Map Knob 2 Oscillator A Index Osc A Map Knob 3 Oscillator A Shift Osc A Map Knob 4 Oscillator A Attack Osc A Map Knob 5 Oscillator A Decay Osc A Map Knob 6 Oscillator A Sustain Osc A Map Knob 7 Oscillator A Release Osc A Map Knob 8 Oscillator A Gain Osc A Map Knob 1 Oscillator B Motion Osc B Map Knob 2 Oscillator B Index Osc B Map Knob 3 Oscillator B Shift Osc B Map Knob 4 Oscillator B Attack Osc B Map Knob 5 Oscillator B Decay Osc B Map Knob 6 Oscillator B Sustain Osc B Map Knob 7 Oscillator B Release Osc B Map Knob 8 Oscillator B Gain Osc B
Since the list of available legacy devices in Reason varies depending on the Reason version, the list of remotable items also varies with the Reason version. As a result, hardware controller manufacturers normally release new Remote maps for their devices whenever Reason is updated.
Because Remote maps are ASCII text files and have an open format, they can also be created by the user, or be overridden in Reason by right-clicking on a control and select the Edit Remote Override Mapping menu option.
For further details about the Remote protocol, see the Remote SDK documentation (available for registered Remote developers) or contact Reason Studios developer support.
Remote™ implementation charts for Rack Extensions
All 45:s must provide a remotable item definition for each property that can be controlled by the user (i.e., that is exposed in the GUI and/or MIDI CC implementation). This allows hardware controller manufacturers (and users) to update their Remote maps to support the new 45.
The remote_implementation_chart
entry in
motherboard_def.lua
associates remotable items with Rack Extension properties. In the example
above, a remotable item with the internal name
Volume
is associated with the volume
property.
See the Jukebox Scripting Specification for details on how to set up remotable items.
Property UI grouping
The ui_groups
table of motherboard_def.lua
allows
you to create groups of logically related motherboard properties for UI
display purposes. The host may use the grouping information in any way
it sees fit, including changing the order of the the groups and the properties
in each group. In the current version of the Reason combinator, for example,
groups are not shown if the motherboard contains fewer than ten automatable
properties.
See the Jukebox Scripting Specification for details on how to set up property UI groups.
Inputs and outputs
A Jukebox 45 can communicate with the outside world via MIDI, and
audio/CV inputs and outputs. These input and output sockets are
defined in motherboard_def.lua
and are read from and
written to by the C++ Realtime code. For receiving and sending MIDI,
see section Receiving and sending notes.
Each input/output are individual property set objects. For example,
the motherboard definition above has two audio outputs, left
and right
, and two CV inputs, note_cv
and
gate_cv
. Both outputs have two properties,
buffer
(the DSP buffer that the Realtime writes to) and
connected
(a boolean property that the host sets to true
when a
cable is connected to the socket). The property path to, say, the
left
socket’s buffer
is
/audio_outputs/left/buffer
Tip! |
For stereo audio input pairs, use the connected property
to set the 45 to mono operation mode if only the left socket is connected. |
Automatic routing
Jukebox allows devices to connect to each other via their input and output sockets. The host may offer different kinds of automatic routing functionality to improve the user experience. In Reason, for example, such functionality come into play when the user connects cables manually (connecting the left socket in an audio stereo pair will automatically connect the right socket), when the "Auto-route" menu option is invoked for a selected device, when a device is added to or deleted from the rack, etc.
In order to facilitate automatic routing, your Jukebox device may provide information about how it should be routed. If it does not provide routing information, the host may not be able to offer automatic routing functionality for the device (in which case the user must connect all sockets manually). The exact automatic routing behavior depends on the host and the current connection state of all involved devices.
To set up automatic routing, you need to provide the following information:
One or several routing hints that describes the general function of your device.
A set of routing targets, i.e., input and/or output sockets on the back panel of your device that the host can use for automatic routing.
Routing pair definitions for all L+R stereo audio socket pairs. It is strongly recommended to supply routing pair definitions for all L+R audio socket pairs even for those pairs that are not part of any routing target definiton.
The motherboard_def.lua
example above defines automatic routing
hints suitable for a stereo instrument.
See the Jukebox Scripting Specification for details on how to set up automatic routing.
Run
Rack Extensions that have an internal sequencer (what we call pattern devices), should implement the Run behaviour. In general, pattern devices should start and stop in sync with master sequencer transport. With Run, the user can start/stop the internal sequencer independently of Reason’s sequencer:
When Reason is stopped, the user can Run the pattern device on it’s own to preview and edit patterns
When Reason is running, the user can pause the pattern device
A good example of the Run behaviour is the Redrum, so give it a spin to get a feel for the behaviour! There are some issues to consider with Run:
Run button should not be automatable. If you want the user to be able to automate the pattern playback, please use a separate property (Sequencer On/Off) or an empty pattern. The reason for this is that we do not want setting the Play position to a PPQ where Run is automated to On to start the device sequencer. Please note that even if the Run button should not be automatable, it should still be a remoteable item.
In Bypass All mode, Players should stop and not listen to Run messages.
The Combinator has a Run Pattern Devices button, that updates the
/transport/request_run
property to all devices in the Combi.
Players: ON/OFF and Bypass All Modes
Players can be disabled in two different ways:
Using the required ON/OFF button on the device front panel
Using the Bypass All switch in the Player Header
These two settings are somewhat overlapping, and how the device is designed can make difference to how it handles the two states. In general it can be thought of like this:
The ON/OFF button should work like turning the power on and off for the device. In OFF mode, the device should only bypass incoming MIDI, and not generate any MIDI or CV of its own. It is recommended that custom displays are cleared or “unlit” to make the device look inactive.
In Bypass All mode, the device should still be responsive (power on, custom displays can be editable), but it is disconnected from the MIDI chain by Reason. It should not generate or process any MIDI or CV, or Run when Reasons transport is running. Even if the device is still ON, we recommend indicating somehow that it is bypassed (for example by writing “Bypassed” in a display or similar).
Motherboard versions and compatibility
An update to a released 45 must keep patches and songs from earlier versions sound and function exactly the same as before. In other words, the default settings of new properties must not affect the sound. In addition, certain rules must be followed to ensure motherboard compatibility:
Properties in the
document_owner
scope andgui_owner
scope that are stored in patches and/or songs must not be removed in the new Motherboard version.The persistence of a
document_owner
property or agui_owner
property may never be reduced, i.e. change from"patch"
to"song"
, or from"song"
to"none"
. (It is allowed to increase from"none"
to"song"
, or from"song"
to"patch"
, however.)Properties cannot change type between Motherboard versions.
MIDI CC and Remote mappings cannot change between Motherboard versions.
The number of steps for discrete (stepped) number properties cannot be decreased between Motherboard versions.
CV and audio inputs/outputs cannot be removed from the new Motherboard version.
CV and audio inputs/outputs must be declared in the same order in both Motherboard versions.
Realtime Controller
The Realtime Controller (RTC) is responsible for serving the C++ realtime code with data, almost like a driver. The most common use of the RTC is to create a native object for the realtime code’s own instance data, often in the form of a C++ class instance that represents the "signal processing engine" of the 45. Other common uses for the RTC is to reconfigure the 45 when the host changes the sample rate, or for loading BLOBs or samples.
The RTC is configured in a Lua file called realtime_controller.lua
.
RTC bindings
The RTC can be used to set up properties that change their value as a
result of changes in one or several other properties. For example,
let’s say that we want to reload a sample when the host changes
the sample rate. To do this, we would create an association between
the /environment/system_sample_rate
property and our
sample property, e.g., /custom_properties/my_sample
. That
way, the my_sample
property will be automatically updated
whenever system_sample_rate
changes. Such derived
properties (which must be rtc_owner
, since they are
modified by the RTC) are updated via Lua functions that you specify in
realtime_controller.lua
.
For example:
rtc_bindings = {
{
source = "/environment/system_sample_rate",
dest = "/global_rtc/change_sample"
},
}
global_rtc = {
change_sample = function(source_path, new_value, old_value)
local new_sample_rate = new_value
local sample_name = ...
jbox.store_property("/custom_properties/my_sample", sample_name)
end,
}
The only information that can be accessed inside a RTC function is:
The input arguments.
The value of the source(s) of the binding (which would be
system_sample_rate
in the example outlined above). You may usejbox.load_property()
to read these values.Standard Lua structures and utility functions.
Any non-Jukebox tables set up in the Lua file.
A subset of the Jukebox Lua toolbox functions.
A RTC function can not have any side-effects, except for updating
properties in the Motherboard. This means that all creation and
updating of global data in the Lua virtual machine is forbidden. If
you need to communicate information between two RTC functions, you
must do so via rtc_owner
properties.
See the Jukebox Scripting Specification for further details.
Sample rate configuration
All 45s must support the following sample rates: 22050, 44100, 48000, 88200, 96000, and 192000 Hz. At least one of these must be supported natively. If the 45 does internal resampling to support one of these rates, that rate should be declared as converted. This allows the host to do sample rate conversion to one of the native sample rates if needed.
See the Jukebox Scripting Specification for details.
Property change notifications to the Realtime
When the Realtime signal processing code is called upon to render
audio, it can read any property in the Motherboard (except those in
the gui_owner
scope). However, the Realtime code is often
only interested in those properties that have changed their values
since the last render callback. One of the arguments to
JBOX_Export_RenderRealtime()
(the audio rendering
function that your 45 makes available and the host calls) is a list of
such property diffs.
For efficiency reasons, the RTC must tell the host which of the
properties in the MOM that the Realtime should receive diffs for.
Those properties are listed in the rt_input_setup
section
of the RTC configuration script. See the Jukebox Scripting Specification
for details.
Realtime
The Realtime sound rendering code of a 45 is written in C++. The 45 must implement and export two functions to the host:
JBox_Export_RenderRealtime()
- Callback for audio renderingJBox_Export_CreateNativeObject()
- Callback for heap memory allocation
The host will continously call JBox_Export_RenderRealtime()
to let the 45 do audio processing. Most of the time, this will happen as
a result of a callback from the operating system audio subsystem, but it
might also be called from other contexts as well, e.g., to do background
processing or stream audio to disk.
Important! |
Updates of document_owner and gui_owner
properties (which are initiated from GUI widgets, including custom displays)
are reported to
JBox_Export_RenderRealtime() once per batch. Property
changes may be merged by the host. In other words, the Realtime has
access to the latest (current) value of such properties at the start
of each batch, but not necessarily all the property changes that
were made since the previous call to
JBox_Export_RenderRealtime() returned. |
It is vital that JBox_Export_RenderRealtime()
carries out its
work quickly; it will be interrupted by the host (and the fuse will be blown)
if it does not return within a certain amount of time.
JBox_Export_RenderRealtime()
may call all C++ functions in
the Jukebox API (see the C++ Specification for details), but it may not
allocate or delete memory on the heap. Note that this also includes
creating instances of standard C++ classes that allocate memory on the
heap, such as std::vector
.
JBox_Export_CreateNativeObject()
is called by the host to
process requests initiated from the RTC Lua functions
jbox.make_native_object_ro()
and jbox.make_native_object_rw()
. Calls to
JBox_Export_CreateNativeObject()
are normally carried out with a lower priority than audio processing. The
host will interrupt the call (and blow the fuse) if it does not return within
a certain amount of time.
JBox_Export_CreateNativeObject()
can only access data
provided as input arguments and it cannot have any side-effects (except
for allocating memory). Therefore, accessing the motherboard from
JBox_Export_CreateNativeObject()
is not allowed. JBox_Export_CreateNativeObject()
may call
all C++ functions in the Jukebox API except for the motherboard access
functions (JBox_LoadMOMProperty()
,
JBox_StoreMOMProperty()
, etc.) and
JBox_GetNativeObjectRW()
.
JBox_Export_CreateNativeObject()
must always return a non-NULL
value.
Important! |
A data block allocated in JBox_Export_CreateNativeObject()
may only contain pointers to addresses within the data block itself,
never to addresses outside the data block! This means that data allocated
in
JBox_Export_CreateNativeObject() must never contain
pointers into sample or BLOB data, for example. Furthermore, the
data block may never contain any TJBox_Value instances,
nor any pointers to TJBox_Value instances. |
The following example illustrates how you can use
JBox_Export_CreateNativeObject()
to create an instance of a
C++ class that represents the 45, and how to use that instance to do
audio processing in JBox_Export_RenderRealtime()
.
realtime_controller.lua
:
rtc_bindings = {
{
source = "/environment/system_sample_rate",
dest = "/global_rtc/new_instance",
},
}
global_rtc = {
new_instance = function(source_path, new_sample_rate, old_sample_rate)
local new_no = jbox.make_native_object_rw("create", {new_sample_rate})
jbox.store_property("/custom_properties/instance", new_no);
end,
}
C++ code:
class CDSPInstance {
public: CDSPInstance(TJBox_Float64 iSampleRate);
public: void RenderBatch();
private:
// Private state goes here.
// Must not contain any TJBox_Value instances
// or external pointers!
};
void* JBox_Export_CreateNativeObject(
const char iOperation[],
const TJBox_Value iParams[],
TJBox_UInt32 iCount)
{
JBOX_ASSERT(strcmp(iOperation, "create") == 0);
JBOX_ASSERT(iCount == 1);
JBOX_ASSERT(JBox_GetType(iParams[0]) == kJBox_Number);
TJBox_Float64 sampleRate = JBox_GetNumber(iParams[0]);
return new CDSPInstance(sampleRate);
}
void JBox_Export_RenderRealtime(
void* iPrivateState,
const TJBox_PropertyDiff /*iPropertyDiffs*/[],
TJBox_UInt32 /*iDiffCount*/)
{
JBOX_ASSERT(iPrivateState != NULL);
CDSPInstance* instance = static_cast<CDSPInstance*>(iPrivateState);
JBOX_ASSERT(instance != NULL);
instance->RenderBatch();
}
C++ toolchain
The Jukebox C++ toolchain is based on a Jukebox-specific variation of the LLVM Clang compiler (see http://llvm.org/) and a collection of supporting tools. This toolchain is used to create LLVM bitcode, which is submitted to Reason Studios as part of the Universal 45. The bitcode is translated to binary format for the various supported native platforms (e.g., Windows and macOS) on the Reason Studios servers before it is being distributed to end-users. This means that the 45 is running platform-specific code on the end user’s machine. To allow the code to run at the required speed for realtime operation, there are limits to the kinds of errors the realtime sandbox can detect.
These errors are caught in realtime (which blows the fuse):
API errors (calling a toolbox function with invalid parameters)
Access violations and other hardware exceptions
Taking too long to respond to a callback from the host
However, during testing and validation, Reason Studios will build submitted 45’s with extra instrumentation code that validates all memory references. This makes it possible to catch a larger amount of bugs during non-realtime stress tests. This means that a 45 built for testing (you select this when you upload the Universal 45 to the Reason Studios servers) will run at a slower speed than a 45 built for deployment.
C++ standard library
The Jukebox SDK provides a limited version of the C/C++ standard libraries. The limitations are mostly due to the requirements of the runtime environment (no direct access to files, etc.) and long term portability issues.
Memory alignment
Memory that you allocate with malloc()
and
new
will be aligned to 16 bytes. However, certain toolbox
functions will perform faster if the memory you provide to them is aligned
on other address boundaries. See the Jukebox Scripting Specification and
the C++ Specification documents for details.
Floating point contract
To ensure portability, robustness, and performance, Jukebox enforces a specific floating point contract. See the Jukebox Scripting Specification for details.
Tip! |
Be careful about operations that depend on precision that lies on the border of the currently used floating point type! Do not compare floating point values for equality, etc. Be aware that single precision floats only have about 7 significant decimal places. |
Latency
If a 45 introduces DSP latency, for example by doing buffered
processing such as FFT, the 45 should tell the host what the current
latency is for each relevant audio and CV output. The current latency
is a per output setting that specifies the latency in frames. All
output sockets have a
rtc_owner
property called dsp_latency
for this
purpose. Note that both audio and CV sockets can have DSP latency specified
and that latency can differ between outputs.
It is allowed to initiate a change of the latency from within JBox_Export_RenderRealtime()
(this requires defining a rt_owner
property in the MOM
and creating a binding to that property in
realtime_controller.lua
), but note that doing so may
introduce audible clicks or breakups in the sound output.
Silence detection
All 45:s must handle silence, i.e., stop rendering when all
inputs are silent (or shortly after inputs become silent). An audio
input is considered to be silent when all values in the buffer are
smaller than or equal to the C++ constant
kJBox_SilentThreshold
. If a 45 does not touch any of the
values in the DSP buffer of the buffer
property in an
audio output socket during a batch, the host considers the socket to
be
silent. Note that filling the buffer with zeros does not
constitute silence in this sense. If the 45 does choose to write to
the DSP buffer, then all values must be written to. It is not
permitted to write nan
:s or inf
:s to the
buffer. See the "Silence Detection Demo" example for details.
Performance optimization
It is important that Rack Extensions do not waste computational resources. A device that uses excessive computational resources may be rejected by Reason Studios.
Here are some basic recommendations for improving performance:
Avoid computational "spikes", i.e., avoid carrying out complex computations in some batches only. Instead, try to spread out complex computations across multiple batches.
Each MOM load/store introduces a small overhead, so try to minimize the number of accesses to motherboard properties.
Do not create GUIs that require updating large numbers of properties (i.e., rt-owned properties that drive
sequence_meter
orvalue_display
widgets) per batch. 50 such property updates per batch is considered expensive, and more than 100 updates per batch is unacceptable in most cases. Also note that the host GUI is always updated at a considerably lower rate than the audio Realtime, so it is not necessary to update properties that drive the GUI every batch. A new alternative for display updates available in SDK 2.5 is the RT owned string. It can be used to send arrays from the DSP a custom display.Do not change RT strings every batch. RT strings are read by a custom display, which will only be updated about 20 times per second. Too frequent RT string updates will blow the fuse.
CV values are only propagated through the engine once per batch, so you should avoid writing to CV outputs more than once per batch. All updates except for the last one carried out in the batch will be ignored by the Jukebox engine.
Avoid subscribing to property changes that your device ignores.
Define as few custom properties as possible in the MOM.
The DSP meter in Reason
The built-in DSP meter in Reason is a useful indicator of the performance of your Rack Extension, but you should be aware of that it does not measure the same thing as a code profiler does.
Reason renders sound in batches of 64 samples. The maximum time (in seconds) that may be spent on computing sound for a batch - let us denote it by Tmax - depends on a number of factors, including the current sample rate, the audio card buffer size, and the CPU and RAM specifications of the host computer. Let us say that the time Reason actually spends computing audio for a batch is T seconds. If T > Tmax, Reason does not have enough computational resources to compute audio fast enough, and the result will be stutter in the output. Conversely, if T < Tmax, Reason has time to "idle" until the next batch has to be computed.
The DSP meter in Reason is essentially a visualization of (Tmax - T): it tells the user how much "idle-time" Reason has available. Double-clicking the DSP meter in Reason Recon brings up more detailed information. But even if there is only one Rack Extension in the rack, the DSP meter value invariably involves many other performance factors too. Therefore, it can be difficult to "separate" performance issues that stem from the Rack Extension from issues that stem from other parts of Reason using the DSP meter alone.
Code profiling
To measure how much time your Rack Extension actually spends in the various functions of your Realtime code, you must use a code profiler tool. When profiling your 45 it is recommended to build a local 45 with compiler optimizations turned on. You can do that by building your local 45 in the Deploymnet configuration.
Profiling on macOS
To profile on macOS, do this:
Build your local 45 in the Deployment configuration. You can do this from Xcode by selecting "Build For" and then "Profiling" from the "Product" menu.
Start Recon.
In Xcode, click the "Xcode" menu, select "Open Developer Tool", and then "Instruments".
In Instruments, select the "Time Profiler" template, attach to the Reason Recon process, and press the record button.
Create an instance of your device and interact with it.
In Instruments, press the stop button. Select the part of the timeline graph where you interacted with your device.
In the Call Tree option view, check the "Hide System Libraries" checkbutton. (This removes irrelevant items from the analysis view.)
To narrow the analysis view to show code paths that involve your DSP code, enter "JBox_Export_RenderRealtime" in the "Involves Symbol" search text box.
Expand the call stacks to drill down to your code functions (you can also check the "Invert Call Stack" to make them appear at the top).
Profiling on Windows
The following steps describes how to profile in Visual Studio 2017 Professional:
Build your local 45 in the Deployment configuration.
Start Recon if necessary and create an instance of your device.
In Visual Studio open the "Performance Profiler" from the Debug menu.
Press "Change Target" (The big button with an icon) and select "Running Process".
Find Reason Recon and press "Select".
Back in the "Performace Profiler" make sure "CPU Usage" is selected and nothing else.
Now press the "Start" button!
Interact with you device.
Press "Stop collection to view CPU usage data" and your profiling data will appear.
Receiving and sending notes
All Jukebox devices are able to receive MIDI notes, with one exception (see "Combinator issues" below). There are 128 MIDI notes [0, 127]. By default, note number 69 corresponds to A440 (i.e., 440 Hz for Middle-A on a standard piano keyboard), but this can be offset by +/- one semitone by the user via the Master Tune setting (see below).
Each MIDI note has a corresponding Jukebox property in the
/note_states
property set object. The property key for each note is set to the note number, i.e.,
/note_states/0 /note_states/1 /note_states/2 ... /note_states/127
The property tag for each note is also set to the note number, so the simplest way to detect note changes is by doing something similar to
void CheckForNoteOn( const TJBox_PropertyDiff iPropertyDiffs[], TJBox_UInt32 iDiffCount) { TJBox_ObjectRef noteStates = JBox_GetMotherboardObjectRef("/note_states"); for (TJBox_UInt32 i = 0; i < iDiffCount; ++i) { if (iPropertyDiffs[i].fPropertyRef.fObject == noteStates) { TJBox_Tag midiNoteNumber = iPropertyDiffs[i].fPropertyTag; TJBox_Float64 velocity = JBox_GetNumber(iPropertyDiffs[i].fCurrentValue); ... } } }
As the example above illustrates, the property value is the velocity of the note. The velocity is a discrete value in the range [0, 127], where a value of 0 means note off and a value of 127 means maximum velocity.
As of Jukebox version 3.0, there is also a C++ toolbox function for converting property values to note events:
... if (iPropertyDiffs[i].fPropertyRef.fObject == noteStates) { const TJBox_NoteEvent& noteEvent = JBox_AsNoteEvent(iPropertyDiffs[i]); ... }
Note that in order to receive note update notifications, you must
subscribe to note_state
changes, i.e., you must say
rt_input_setup = { notify = { "/note_states/*", } }
in realtime_controller.lua
.
Tip! |
Although tags must be unique for all properties within a property
set object, it is possible for two properties in different
objects to have the same tag value. Be careful so you do not confuse
properties in the note_states object with properties
in the custom_properties
object! |
Player devices have the ability to send note events using the function JBox_OutputNoteEvent
.
Player devices, for certain product designs, should forward incoming MIDI note events to the next device (another Player or the instrument). Player devices in off mode should always forward incoming note events. The following example shows how to forward incoming MIDI note events:
void ForwardNoteEvents( const TJBox_PropertyDiff iPropertyDiffs[], TJBox_UInt32 iDiffCount) { TJBox_ObjectRef noteStates = JBox_GetMotherboardObjectRef("/note_states"); for (TJBox_UInt32 i = 0; i < iDiffCount; ++i) { if (iPropertyDiffs[i].fPropertyRef.fObject == noteStates) { const TJBox_NoteEvent& noteEvent = JBox_AsNoteEvent(iPropertyDiffs[i]); JBox_OutputNoteEvent(noteEvent); } } }
Tuning
To convert from MIDI note to frequency (Hz), you can use this formula:
TJBox_Float32 frequency = std::powf(2.0f, (note - 69.0f) / 12.0f) * 440.0f;
To convert from frequency (Hz) to MIDI note, you can use this formula:
TJBox_Float32 log2(TJBox_Float32 n) { return std::logf(n) / std::logf(2.0f); } ... TJBox_Float32 note = 69.0f + 12.0f * log2(frequency / 440.0f);
Master Tune
Reason follows the convention that the octave is divided into 12 semitones of 100 cents each. The Master Tune setting offsets the frequency of A440 (MIDI note 69) in cents. It is assumed that all other notes are offset accordingly.
Important! |
All instruments must respond to the Master Tune setting. |
The /environment/master_tune
property (kJBox_EnvironmentMasterTune
property tag) contains the Master Tune setting in the range [-100,100].
To apply master tuning, you can do this:
TJBox_Float64 mtProp = ...; // Value of "/environment/master_tune" property TJBox_Float32 note = ...; // MIDI note to play TJBox_Float32 mt = static_cast<TJBox_Float32>(mtProp) / 100.0f; TJBox_Float32 frequency = std::powf(2.0f, ((note + mt) - 69.0f) / 12.0f) * 440.0f;
or you can modify the frequency after it has been computed:
TJBox_Float64 mtProp = ...; // Value of "/environment/master_tune" property TJBox_Float32 frequency = ...; // Frequency to be modified by master tune TJBox_Float32 mt = static_cast<TJBox_Float32>(mtProp) / 100.0f; const TJBox_Float32 kSemiTone = std::powf(2.0f, 1.0f / 12.0f); frequency *= std::powf(kSemiTone, mt);
Combinator issues
Reason’s Combinator device allows the user to group together different devices into a single "meta-instrument".
45:s where the device_type
is set to
"instrument"
in info.lua
will receive notes in Combinators by default. 45:s where
device_type
is set to one of the other values do not receive notes in Combinators
by default. The user can override this by unchecking the "Receive Notes"
checkbox in the Combinator programmer, unless the
accepts_notes
flag has been set to false
in info.lua
. If
accepts_notes
is false
, the 45 will never be able to receive notes
inside Combinators, and the "Receive Notes" checkbox will be
disabled in the Combinator programmer.
Sequencer integration
As of version 3.0, Jukebox supports sequence-based and pattern-based Rack Extensions. A 45 can receive information about the current master transport play position, tempo, loop positions, bar start position, and whether the sequencer is running or not. This makes it possible to create Rack Extensions with tempo-synced LFOs, patterns, and similar.
However, it is very difficult or even impossible to create Rack Extensions that react correctly to the absolute master transport position.
The 45 can obtain the master transport play position at the start of
each batch (of 64 frames). The reason is, quite simply, that
JBox_Export_RenderRealtime()
is only called once per batch,
and that any property value that it obtains from Jukebox (with the exception
of notes) is from a "snapshot" of the MOM as it was at the start of the
batch.
The notes that are received within a batch are normally located just
to the right of the master transport play position in the sequencer.
But if the sequencer happened to reached a loop point during the
batch, or if the user has moved the transport position explicitly, the
play position may actually have jumped during the batch. If so, the
notes reported to
JBox_Export_RenderRealtime()
may actually originate from completely
different locations in the sequencer. Indeed, although unlikely, the play
position may actually have jumped more than once during the batch.
As a result, a 45 can only "react" to transport play position jumps "after the fact". In practice, this often leads to artefacts like dropped notes or "untight" triggering of notes.
Pattern and Pattern Clips
Devices with a built-in sequencer can have different patterns (separate sequences or rhythms). To allow the user to control this, Jukebox has special Pattern Automation properties and clips. This system is based on the original pattern system in for example Redrum, and supports up to 32 patterns. See the Jukebox Scripting Specification for details.
CV standard in Reason
CV sockets contain a floating point value that represents the CV signal for the current batch. For compatibility with other Reason devices, it is important to follow the CV level standards used by Reason.
Type | Nominal range | Description |
---|---|---|
Unipolar |
[0.0, 1.0] |
Reason standard unipolar signals, as produced by the Matrix in the corresponding mode. |
Bipolar |
[-1.0, 1.0] |
Reason standard bipolar signal, as produced by the Matrix in the corresponding mode. |
Thor Bipolar |
[-0.5, 0.5] |
Thor internally uses this CV range for bipolar values, which is exposed on CV outputs when connected to such sources. |
Note/Gate (velocity) CV |
[0.0, 1.0] |
This is much like Unipolar, but quantized to 127 steps to fit the legacy note and velocity resolution of MIDI. |
Thor frequency control |
[-1.0, 1.0] |
7 octaves / volt pitch control signal. |
The range specified for a CV type represents the signal level needed for nominal full range modulation of the corresponding parameter. That is, a full range LFO producing standard Matrix bipolar output should result in a signal in range [-1.0, 1.0] on the corresponding CV output.
Typically, the nominal full range corresponds to 100% modulation, but it is up to the receiving device to decide what levels are usable. A CV mixer might allow signals greater than 1.0 for instance, and some devices might allow modulating filter frequencies to 130%.
Devices with CV inputs are responsible for clamping incoming CV signals to a useful range before mapping to internal parameters.
It is common for CV inputs to have a trim knob that can be used to scale the signal before clamping and mapping. The scaling applied by trim knobs ranges from 0.0 to 1.0. By default, trim knobs are set to 1.0.
As described above, note and velocity values in Jukebox have the same range as in the MIDI standard, [0, 127]. To convert from a note or velocity value to CV, divide the value by 127:
TJBox_Float32 noteCV = noteValue / 127.0f; TJBox_Float32 velocityCV = velocity / 127.0f;
For historical reasons, the conversion from CV to note or velocity must be done slightly differently. Please use the following conversion to ensure consistency with Reason’s legacy devices:
// Truncates a float value to an integer and // limits the result to [lower, upper] TJBox_Int32 clamp(TJBox_Float32 in, TJBox_Int32 lower, TJBox_Int32 upper) { ... } ... TJBox_Int32 noteValue = clamp(noteCV * 127.0f + 0.1f, 0, 127); TJBox_Int32 velocity = clamp(velocityCV * 127.0f, 0, 127);
Velocity CV is commonly called "Gate" since it is used for monophonic triggering as well as transferring velocity information. Jukebox devices that react to gate should trigger on the positive flank of the velocity signal’s corresponding integer value. That is, when the value of the gate CV changes from below 1/127 to 1/127 and above.
Important! |
Instrument devices must retrigger notes if the note CV changes while the gate is open (1/127 and above). |
Here is a code snippet that illustrates how notes are triggered in the built-in Reason devices:
// Only call this function when GateInputCV is connected. void HandleGateCVInput( TJBox_Float32 iNoteCVInput, // Value of the note CV input TJBox_Float32 iGateInput, // Value of the gate CV input TJBox_Bool iNoteCVInputIsConnected, // Is note CV input connected? TJBox_Int32& ioPrevNote, // IN/OUT: Previous note value TJBox_Int32& ioPrevVelocity) // IN/OUT: Previous velocity value { TJBox_Int32 velocity = static_cast<TJBox_Int32>(iGateInput * 127.f); if (velocity < 0) { velocity = 0; } else if (velocity > 127) { velocity = 127; } // If only gate is connected, always use note 64. TJBox_Int32 newNote = 64; TJBox_Bool trigged=false; if (ioPrevVelocity > 0 && velocity == 0) { HandleAllNotesOff(); // Kill all notes trigged from CV-Gate. } if (iNoteCVInputIsConnected) { // Slightly odd rounding for compability. See text above. newNote = static_cast<TJBox_Int32>(iNoteCVInput * 127.f + 0.1f); if (newNote < 0) { newNote = 0; } else if (newNote > 127) { newNote = 127; } // Trig when note is changing, even if no new gate. if (newNote != ioPrevNote) { if (velocity > 0) { ioPrevNote = newNote; HandleNoteOn(newNote, velocity); trigged = true; } } } // Normal trig from gate. if (ioPrevVelocity == 0 && velocity > 0 && !trigged){ ioPrevNote = newNote; HandleNoteOn(newNote, velocity); } ioPrevVelocity = velocity; }
The standard for pitch control in Thor is slightly different, and is used internally for both controlling the pitch of oscillators and the cutoff frequency of filters. Thor uses a 7 octave per volt mapping where zero volts corresponds to middle C.
const TJBox_Float32 kMiddleC = 261.6256f; TJBox_Float32 frequencyHz = std::powf(128.0f, frequencyCV) * kMiddleC;
Audio reset requests
Sometimes, the host may want to re-initialize the 45 to a "silent" state. The most common situation is just before a song or loop is exported to an audio file. In such situations, any still-sounding reverb tails or synthesizer voices should be silenced immediately. (Otherwise, the tail or voice release will be heard at the beginning of the exported audio.) Note that the host may request an audio reset in other situations, such as when the device is first created, or when the sample rate changes.
To signal that a 45 should reset itself, the host sets the property
/transport/request_reset_audio
to new value. The property
is an integer counter with an unspecified non-negative initial value. The
45 should reset itself at the batch frame where the property was updated,
and then continue processing as normal for the following frames in the
batch.
3D GUI specification
Important! |
3D GUI has been deprecated in RE SDK version 4.2.0 |
[[2D_GUI_Specification]] == 2D GUI Specification
You may optionally choose to define your Rack Extension GUI using high-resolution 2D images. This gives you more freedom in choosing how those images are produced, but may make it more difficult to create a device that "fits" the Reason rack.
The 2D GUI Designer Manual contains detailed information on how to
create 2D GUIs for Jukebox. The format of the hdgui_2D.lua
script
is specified in the Jukebox Scripting Specification document.
Custom displays
Jukebox SDK 2 introduces a new widget type, custom display. A custom display is a flat rectangular surface on the panel that the 45 can draw into using Lua code. The 45 can also receive user gestures via the custom display widget and use Lua code to turn those gestures into Motherboard property changes.
Once a display has been bound to a property it will receive a notification whenever that property is changed (e.g., via another widget or automation). A custom display may only modify properties that it is bound to.
You can bind properties from all four owner scopes to a custom display (gui_owner
, document_owner
, rtc_owner
, and
rt_owner
), but the display can only write to properties in
the gui_owner
and document_owner
scopes. (The display can read the value of all properties that it is bound
to.)
Motherboard snapshots for custom displays
When a custom display is redrawn, it is sent a snapshot of the
Motherboard state, i.e., it is sent a local copy of the values of all
properties that are bound to the display. Jukebox does not guarantee
any specific update rate for custom displays, nor does it guarantee
that all values in the Motherboard snapshot originated in the same JBox_Export_RenderRealtime()
batch.
For example, if an rt_owner
property is bound to a custom
display and it is modified by
JBox_Export_RenderRealtime()
, the host flags the display
as dirty (assuming the modified value is different from the last time
the display was redrawn). The property may be modified several times
before the host has time to actually redraw the display. The snapshot
of the Motherboard is taken just before the display is redrawn.
Drawing into custom displays
A custom display has a virtual "video memory" that consists of a rectangular buffer of "virtual pixels" that the 45 can modify. The width and height of the video memory cannot change at run-time. When the display (or a part of it) must be repainted, the corresponding area in the video memory is cleared to black, or to a (static) bitmap that the 45 provides. It is not possible to read from the video memory.
To create a custom display, you first need to add a flat, rectangular
surface to the device’s front or folded front panel (custom
displays cannot be added to the back panels), and assign a custom
display material to it (see the GUI Designer Manual and Jukebox
Scripting Specification for details). Then, you create a custom
display widget in hdgui_2D.lua
similar to this:
jbox.custom_display{
graphics={
main_node="/root/Display/Background",
},
display_width_pixels = 200,
display_height_pixels = 160,
invalidate_function="invalidate",
draw_function="draw",
gesture_function="gesture",
values={
"/custom_properties/property_1",
"/custom_properties/property_1"
},
},
The graphics
entry is the same as all other widgets,
except that it cannot have boundaries
or
hit_boundaries
modifiers.
The display_width_pixels
and
display_height_pixels
entries are the dimensions of the "video
memory".
The invalidate_function
is the name of an Lua function in
a file called display.lua
(that you provide). The host
will call this function whenever one or several of the properties that
have been bound to the display have changed. The invalidate function
is responsible for informing the host about which areas of the display
that need to be repainted, based on the property changes. This is done
by calling the Lua toolbox function
jbox_display.invalidate()
once for each rectangle in the display
that needs to be redrawn. For example:
function invalidate(property_values, last_property_values, display_info)
-- display_info contains information about the display
local w = display_info.width
local h = display_info.height
-- check if a property value has changed and invalidate the display
if property_values[1] ~= last_property_values[1] then
local rect = { left = 0, top = 0, right = w / 2, bottom = h / 2 }
jbox_display.invalidate(rect)
end
...
end
If the invalidate function does not make any calls to
jbox_display.invalidate()
, the host will assume that the
display does not have to be repainted.
Tip! |
Providing an invalidate function is optional, but it is important to remember that it is a useful tool for performance optimization. If your 45 does not provide an invalidate function, the host has to repaint the entire display whenever a display-bound property changes. |
The draw_function
is the name of a Lua function (also in
display.lua
) that the host calls if the display must be
repainted. The host passes the rectangle that needs to be repainted
(the "dirty rect") to the function. Note that the dirty rect may not
be the same rectangle that the
invalidate_function
provided (the entire display has to be
repainted when it is drawn for the first time, for example). The display
function is also provided with the current values for all properties that
are bound to the display. For example:
function draw(property_values, display_info, dirty_rect)
-- use a property value to compute a color and fill the dirty rect
local color = {r = property_values[1] * 255, g = 0, b = 0}
jbox_display.draw_rect(dirty_rect, color)
end
The invalidate function and the draw function may not have any side-effects, except for updating the virtual video memory. You cannot pass information between the invalidate and draw functions, or between them and the rest of Jukebox, except via Motherboard properties.
The gesture_function
responds to user gestures; it is described
in detail below.
The values
entry is an indexed-based table that contains
the Motherboard properties (in the gui_owner
or
document_owner
scopes) that the display should "listen to"
and/or update.
Tip! |
You can re-use the same invalidate, draw, and gesture functions
for different displays (in the same 45) by providning different
sets of properties in the values table. |
Drawing details
The Jukebox Lua toolbox contains a number of functions that you can use to draw lines, rectangles, polygons, and text into the custom display. The coordinate system that is used for drawing follows the convention that the coordinates are "between" pixels, as illustrated in the figure below.
All drawing commands create antialiased primitives. For example,
using the
jbox_display.draw_line()
toolbox function to draw a rectangle
that is one pixel thick between two 2D coordinates will result in something
similar to this:
The pixels that the (mathematical) 1-pixel-wide rectangle covers will be colored according to the area that is covered by the rectangle.
Jukebox does not guarantee that the results of a drawing command will be pixel-equivalent on different hosts or operating systems.
Tip! |
To draw "non-antialiased" horizontal and vertical lines, add 0.5 to all x and y coordinates. |
All drawing commands accept RGBA colors in the sRGB color space. Alpha blending works like the "Normal" layer blend mode in Photoshop, i.e.,
RGB = RGBin x Ain + RGBpixel x (1 - Ain)
where RGB is the new color of a pixel in the video memory, RGBpixel is the current color of that pixel, and RGBAin is the color that you pass to the drawing toolbox function.
Adding glass
Most displays look more realistic if there’s a glass pane in front of them. To add a glass in front of a custom display, position a transparent geometry in front of the display. Then create a static decoration widget and point to the glass geometry in its widget definition. See the GUI Designer Manual and Jukebox Scripting Specification for details.
Responding to user gestures
As mentioned above, it is possible to configure a custom display to receive user gestures. The display does not work directly with window system events, this is the host’s responsibility. Instead, the host will interpret incoming window system events as more abstract gestures that it then forwards to the custom display.
To receive user input, you provide the host with the name of a gesture recognizer
function, which is a Lua function that you define in
display.lua
. If the gesture recognizer function decides
that the custom display should respond to the gesture, it returns a
gesture definition table. (If not, the gesture recognizer
function must return nil
.)
The gesture definition table contains two entries:
The name of one or several gesture handler callbacks that the host should call as the gesture evolves.
A table containing custom data that the 45 can use to configure the gesture, or for passing information between the gesture handler callbacks.
The gesture handler callbacks are allowed to update any number,
string, or boolean property in the gui_owner
or
document_owner
scope in the
custom_properties
property set object, assuming that the property
has been bound to the custom display. (Performance properties cannot be
modified by custom displays.)
Here is an example. We begin by specifying a gesture recognizer
function in
hdgui_2D.lua
:
jbox.custom_display{
...
gesture_function="gesture",
...
},
In this example, the display always responds to tap/clicks (it doesn’t use any custom data):
function gesture(property_values, display_info, gesture_start_point)
return {
handlers = {
on_tap = "handle_tap"
}
}
end
The gesture handler, handle_tap
, updates one of the
properties that are bound to the custom display:
function handle_tap(property_values, display_info, gesture_info, custom_data) return { gesture_ui_name = jbox.ui_text("click") property_changes = { [1] = gesture_info.current_point / display_info.width }, } end
The gesture_ui_name
is a localized string that the host uses
to present the gesture to the user, e.g., in its undo system.
The details of how undo works is host-specific. Here is how it currently works in Reason:
All property changes are always recorded in the undo system, but only changes for
document_owner
properties are actually presented to the user.One complete gesture always corresponds to one undo step.
If you return different
gesture_ui_name
strings from the gesture handler callbacks, the first one returned is used to identify the gesture in Reason’s undo meny item.If you change one or several
document_owner
properties in the gesture handler callbacks, the gesture becomes visible in Reason’s undo system.If you change one or several
document_owner
properties and one or severalgui_owner
properties in the gesture handler callbacks, the gesture becomes visible in Reason’s undo system.If you only change
gui_owner
properties in the gesture handler callbacks, the gesture is not visible in Reason’s undo system. Anygesture_ui_name
string returned is ignored.