Back to top

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.

Figure 1

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

Figure 2

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

Figure 3

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

Figure 4

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), and

  • JBox_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:

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}

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:

  1. Using the required ON/OFF button on the device front panel

  2. 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 and gui_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 a gui_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 use jbox.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 rendering

  • JBox_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 or value_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.

coordsys

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:

line

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:

  1. The name of one or several gesture handler callbacks that the host should call as the gesture evolves.

  2. 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 several gui_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. Any gesture_ui_name string returned is ignored.