HISE Docs

Broadcaster


The Observer Pattern is a very common software pattern and is used throughout most of the codebase in JUCE and HISE. It allows you to register objects that will be notified when anything changes and is super helpful for organising bigger projects.

Definition

In HiseScript you can implement the pattern manually by having an array that stores functions like this:

// Internal: 
var registeredFunctions = [];
var currentValue = 0;

// register a function with a single argument
function addListener(lf)
{
	registeredFunctions.push(lf);
	
	// We call it once at registration, so it's up to date...
	lf(currentValue);
}

function sendMessage(value)
{
	// We only call the listener if the value has actually changed
	if(value != currentValue)
	{
		currentValue = value;

		for(f in registeredFunctions)
			f(value);
	}
}

// Usage:
addListener(function(newValue)
{
	Console.print(newValue);
});

sendMessage(90);

Use cases of the Observer pattern

This pattern is mostly useful in GUI logic coding, eg. page-handling or displaying any information at multiple places (eg. which user preset is loaded). The advantage of this is that you can implement localised tasks at very narrow scope which are then automatically executed when the global state changes. Let's take a look at the previously mentioned example: page handling. Without the observer pattern, you would normally do it like this:

const var buttons = [Content.getComponent("Page1Button"),
                     Content.getComponent("Page2Button"),
                     Content.getComponent("Page3Button")];

const var pages =   [Content.getComponent("Page1"),
                     Content.getComponent("Page2"),
                     Content.getComponent("Page3")];

inline function onPageButton(button, value)
{
	local idx = buttons.indexOf(button);
	
	for(p in pages)
		p.set("visible", pages.indexOf(p) == idx);
}

for(b in buttons)
	b.setControlCallback(onPageButton);

which is super slick and concise in this minimal example, but it quickly gets messy in real world projects, where you want additional tasks being performed when the page changes, eg:

All these functionality has to be tucked into the poor little button callback which now gets super convoluted. With the observer pattern, the example looks like this:

// here is the observer code from the definition above...
var registeredFunctions = [];
var currentValue = [];
function addListener(f) { ... }
function sendMessage(value) { ... };

const var buttons = [Content.getComponent("Page1Button"),
                     Content.getComponent("Page2Button"),
                     Content.getComponent("Page3Button")];

const var pages =   [Content.getComponent("Page1"),
                     Content.getComponent("Page2"),
                     Content.getComponent("Page3")];

inline function onPageButton(button, value)
{
	local idx = buttons.indexOf(button);
	sendMessage(idx);
}

for(b in buttons)
	b.setControlCallback(onPageButton);         
	
addListener(function(idx)
{
	for(p in pages)
		p.set("visible", pages.indexOf(p) == idx);
});

On first sight it looks more verbose than the previous example (and if it's as simple as that then it's a good example of overengineering), but this solution scales much better in real world projects, because now you can attach new listeners whenever you implement something at the very location you want:

// ... deep in some other namespace

// Now if you want to perform more tasks, you can leave the button callback alone and
// add it at the very scope you're working on.
addListener(function(idx)
{
	local colours = [Colours.red, Colours.blue, Colours.green];
	doSomethingWithColours(colours[idx]);
})

However, with the stock HiseScript solutions there are a few disadvantages:

The Broadcaster object tries to address these issues by giving you (basically) the same functionality as shown in the script above, but with a clean interface, async callback support.

In order to use it, call Engine.createBroadcaster with the default values and then use one of its methods to implement the observer pattern:

// If you pass in a JSON object into the constructor, you can
// access the properties later using the standard dot operator.
const var bc = Engine.createBroadcaster({"myProperty": 12});

bc.addListener("testFunction", function(index)
{
	Console.print(index);
});

// Access the property as if the broadcaster would be a generic JSON object:
Console.print(bc.myProperty);


// Setting the property sends a (synchronous) message to the listeners
bc.myProperty = 90;

// This line does the same as the one above...
bc.sendMessage([90], false);

However, calling sendMessage() or assigning properties manually from your code is just one of the ways that this class can be used: you can also register it to any callback or even attach it to internal event sources that weren't accessible before.

Compatibility with callback slots

In HiseScript, there are many callback slots that can be registered with a function (or inline function), eg:

However instead of a reference to a function, you can also pass in a Broadcaster object, and it will then call its listeners (asynchronously) everytime the callback happens.

Be aware that in this case the parameter amount defined by the argument in Engine.createBroadcaster() must match the expected argument amount.

Attachable Event Sources

Another feature that vastly increases the usefulness of this object is the ability to register it to internal event types that were not accessible in HISE before:

Value changes

On the first look this doesn't sound particularly interesting because the value callback was already accessible through setControlCallback() , however if you're using processorId / parameterId properties it will not fire so this gives you the chance to add additional, "non-exclusive" callbacks for UI things

See: attachToComponentValue()

Property changes

This is super helpful if you want to react on changing properties (eg. the visible flag) of certain components.

See: attachToComponentProperties()

Mouse events

This gives you the ability to attach custom mouse callbacks to ANY component using the same interface as ScriptPanel.setMouseCallback() This will not override the default mouse behaviour but rather give you the option to customize the user interface and eg. show certain things while a slider is being dragged.

See: attachToComponentMouseEvents()

Metadata

There is another concept of the broadcaster system which is tightly coupled with the visualisation of the broadcaster connections and this is the Metadata . Almost all methods which will generate an item on the BroadcasterMap (so the broadcaster itself but also all listeners and event sources) have a metadata parameter in their function signature that needs to be populated with a description of what this item is doing. This enforces self-documenting code on the coding level, which is fine but also heavily increases the usability of the broadcaster map which will help tremendously at keeping the overview over large projects.

A metadata object can be a simple string that only contains a description of the item, but for more information you can use a JSON object containing these properties:

Property Type Description
id String a (unique) String that is used as name for the item. This doesn't need to be a variable name so you can use any human-readable title here. If you don't supply a JSON object but a simple string, this string will be used as id (making this the only non-optional property in this list).
comment String A markdown formatted string which will be shown on the broadcaster map. There is one little magic trick applied here and that is that if you comment the function call or object definition with a /** comment (instead of the default /* one), it will parse the comment from the code and write it into the metadata as comment property.
colour int A colour value that is used for the item drawing. You can supply a colour value using the 0xAARRGGBB notation, or just enter -1 , then it will create a random colour from the ID hash, which is a quick way to colour an item.
tags Array of strings You can attach tags to any item and then filter the broadcaster map to only display the items you want. This helps navigating around big projects.
priority int This property is only valid when used with a listener item and defines the order of how the listeners are called. By default they are called by the order of the addListener call, but if that is not what you want, you can shuffle around the listeners by supplying a priority (higher priority values means that the items are moved up the list and the default value is 0 ).
args Array of strings This is only valid for broadcaster definitions and contains a list of strings describing the arguments of this broadcaster.

For an example usage take a look at the various API calls.

Class methods

addComponentPropertyListener

Adds a listener that sets the properties of the given components when the broadcaster receives a message.

Broadcaster.addComponentPropertyListener(var object, var propertyList, var metadata, var optionalFunction)


This function will change component properties (like visible , enabled , itemColour2 etc.) when the broadcaster sends a message. It basically is the same as adding a function call with addListener() and changing the properties inside this call, however there are a few advantages over this approach:

Content.addKnob("Knob1", 0, 0);
Content.addKnob("Knob2", 0, 50);
Content.addKnob("Knob3", 0, 100);
Content.addKnob("Knob4", 0, 150);

/** Create a broadcaster. We need 3 arguments to attach it to component properties. */
const var pb = Engine.createBroadcaster({
	"id": "Property Syncer",
	"colour": -1,
	"args": ["component", "property", "value"]
});

/** Attach it to react on changes of the `x` property of `Knob1`. */
pb.attachToComponentProperties("Knob1", "x", "X-Position Watcher");

/** This function just syncs the `x` property by returning the value but you could calculate any custom value if you need to. */
inline function updateFunction(indexInList, component, property, value)
{
	Console.print(trace({
		"indexInList": indexInList,
		"component": component.get("id"),
		"property": property,
		"value": value
	}));
	
	// something to play around with...
	//return value * (indexInList + 2);
	
	return value ;
};


/** You could also just pass in this instead of the updateFunction, then it will use the "default" behaviour. */
const var defaultUpdateFunction = 0;

/* Add a listener that changes properties when the broadcaster sends out a message. */
pb.addComponentPropertyListener(["Knob2", "Knob3", "Knob4"], // The array of knobs that should be synced
                                "x",                         // the properties that you want to sync
                                "update X position", updateFunction);


/* this will do the same thing but not as elegant. */
pb.addListener([Content.getComponent("Knob2"),
				Content.getComponent("Knob3"),
				Content.getComponent("Knob4")],
				"update X position manually",
function(component, property, value)
{
	//for(c in this)
	//	c.set(property, value);
});

This is the visualisation of the code above. You can see that the context awareness of the first listener item yields much more information to be displayed which gives you a quick way to ensure the correct functionality:

This will add a target to the broadcaster that will change component properties when the broadcaster receives a message. It can be used for synchronising properties, changing multiple properties of a list of components with all the benefits of the broadcaster system. The function expects these arguments:

Argument Type Description
object Single value or list of strings (component IDs) or script references the target components which properties are supposed to be changed.
propertyList Single value or list of property IDs the target properties that are supposed to be changed.
metadata String or JSON object a metadata object that contains some information for the broadcaster map.
optionalFunction Callable object An optional function that determines the value that should be sent to each component property (see below). If this argument is not a function, the broadcaster needs to have three properties (component, property, value) and will just send out the incoming value to the targets (which is an easy way of synchronizing properties.

The optional function

If you supply a function as last argument, it will be called for every target component and property to figure out which value to send. The function signature needs to have all parameters of the broadcaster and a integer index at the first position that will contain the index of the component in the list that was passed in.

const var bc = Engine.createBroadcaster({
	"id": "MyBroadcaster",
	"args": { "firstArg": undefined, "secondArg": undefined, "thirdArg": undefined }
});

// This function needs to have an index parameter and then as much parameters as
// the broadcaster is using (in our case three).
// It will then be called for each property and component with the knobIndex argument
// containing the index of the component to change. The function's return value will 
// be sent as property.
inline function setKnobColours(knobIndex, a1, a2, a3)
{
	if(knobIndex == 0)
	{
		return calculateTheColourForTheFirstKnob();
	}
	if(knobIndex == 1)
	{
		return calculateTheColourForTheSecondKnob();
	}
	// ...
}

bc.addComponentPropertyListener(["Knob1", "Knob2", "Knob3"],      // targets
								["itemColour", "itemColour2"],    // properties
								{ "id": "set both itemColours"}), // metadata
								setKnobColours);				  // optionalFunction

Be aware that the value returned by the function will be sent to all properties but if you want to send different values to different properties, you can call this function again with another function for each property.


addComponentRefreshListener

Adds a listener that will cause a refresh message (eg. repaint(), changed()) to be send out to the given components.

Broadcaster.addComponentRefreshListener(var componentIds, String refreshType, var metadata)


This function adds a listener to the broadcaster that will send a refresh message of the given type to all components defined by componentId parameter. The refreshType parameter defines the type and must be one of the following strings:

This function is more or less equivalent to something like

bc.addListener(componentList, "repaint components", function(index)
{
	for(c in this)
		c.sendRepaintMessage();
});

but is less to type, a bit faster (because it doesn't have to evaluate the script function) and more versatile. And you get a nice visualisation in the Broadcaster map that blinks everytime the refresh messages are sent.

Example

This example will send a message when you click a button and causes a list of Panels to repaint themselves.

const var button = Content.addButton("Button1", 0, 0);

const var PanelArray = [Content.addPanel("Panel4", 0, 50),
                        Content.addPanel("Panel3", 100, 50),
                        Content.addPanel("Panel2", 200, 50),
                        Content.addPanel("Panel1", 300, 50)];

for(p in PanelArray)
{
	p.setPaintRoutine(function(g)
	{
		g.setColour(Colours.withAlpha(Colours.white, Math.random()));
		g.fillRect(this.getLocalBounds(0));
		
	});
}

const var bc = Engine.createBroadcaster({
	"id": "RepaintBroadcaster",
	"colour": -1,
	"args": ["index"]
});


bc.addComponentRefreshListener(PanelArray, "repaint", "Repaint Panels");

inline function onButton(component, value)
{
	// just send out any value to trigger the broadcaster
	bc.index = Math.random(); 
}

button.setControlCallback(onButton);


addComponentValueListener

Adds a listener that sets the value of the given components when the broadcaster receives a message.

Broadcaster.addComponentValueListener(var object, var metadata, var optionalFunction)


This call sets the value (and causes changed() to be called) whenever a broadcaster message is sent. The syntax is pretty similiar to the addComponentPropertyListener function (without the property argument, which isn't required obviously).


addDelayedListener

Adds a listener that will be executed with a delay.

Broadcaster.addDelayedListener(int delayInMilliSeconds, var obj, var metadata, var function)


This function is very similar to the normal addListener function, but you can supply a millisecond value that will be used to delay the function.

Be aware that the execution of this function is not queued, so whenever you send a new message in the interval, it will just reset the timer and discard the pending function call.

addListener

Adds a listener that is notified when a message is send. The object can be either a JSON object, a script object or a simple string.

Broadcaster.addListener(var object, var metadata, var function)


This registers a listener to the broadcaster which will be notified whenever the broadcaster's state changes.

The function expects three parameters. The second parameter needs to be either a function (or inline function) with the exact same amount of parameters as the broadcaster's default value amount (defined by the constructor).

The first parameter can be one of three things:

the second parameter is a metadata parameter that is used to display the target and set other properties (eg. priority)

This will be used in order to identifiy the listener (so if you want to remove it, you need to use the same value).

As an additional feature, it will be also accessible using this in the function callback:

const var b = Engine.createBroadcaster({
	"id": "My Broadcaster",
	"args": ["index", "isTrue"]
});

b.addListener({"id": "MY_ID"}, "some description about the target",
function(index, isTrue)
{
	Console.print(this.id); // "MY_ID"
});

b.addListener("funky_time", "some other target",
function(index, isTrue)
{
	Console.print(this); // "funky_time";
});


const var Knob = Content.addKnob("Knob1", 0, 0);

/** Here we are using a JSON object instead of a metadata string
	to set the priority. Note how the listener is at the top of the list
	despite being added as last target. 
*/
b.addListener(
Knob, 
{
  "id": "Print the knob value", 
  "colour": 0xFF3388AA, 
  "priority": 10 
},
function(index, isTrue)
{
	Console.print(this.getValue());
});

Note that the function will be called (synchronously) when you register it so that the listener is updated to the current value.

addModuleParameterSyncer

Adds a listener that will sync module parameters from the attached module parameter source. Edit on GitHub

Broadcaster.addModuleParameterSyncer(String moduleId, var parameterIndex, var metadata)



attachToComplexData

Registers this broadcaster to be notified when a complex data object changes.

Broadcaster.attachToComplexData(String dataTypeAndEvent, var moduleIds, var dataIndexes, var optionalMetadata)


If you want the broadcaster to be notified whenever an event occurs with a complex data type (SliderPacks, Tables or AudioFiles), you can use this method to attach the broadcaster to one or more data objects.

In order to attach a broadcaster to a complex data object using this method, it needs to have exactly 3 arguments defined in its args metadata property.

The first arguments dataTypeAndEvent is a String that describes the event type and datatype you want to listen to. The syntax for the string is DataType.EventType with the following options for the DataType part:

and the following options for the EventType part:

The moduleIds argument is either a String or an Array of Strings with the processor IDs that are holding the data types.
The dataIndexes argument is either an integer index (zero based) or an array of zero based integers for each data type.

This means that the amount of data types that you attach the broadcaster to is defined by
NumberOfModules * NumberOfIndexes .

optionalMetadata is a metadata object used by the broadcaster map.

// You need three arguments 
const var bc = Engine.createBroadcaster({
	"id": "Complex Data Listener",
	"colour": -1,
	"args": ["processorId", "dataIndex", "value"]
});

bc.attachToComplexData("Table.Display", 
					   ["LFO Modulator1", "LFO Modulator2"], 
					   0, 
					   "Connect to 2 LFO table rulers");

bc.attachToComplexData("SliderPack.Content",
					   "Arpeggiator1",
					   [0, 1, 2],
					   "Connect to changes for every slider pack of an arp");
	   
bc.attachToComplexData("Table.Content",
					   ["Table Envelope1", "Table Envelope2"],
					   [0, 1],
					   "Connect to table edits for every table 
					   (attack & release) for two table envelopes");

Once you've attached a broadcaster to a complex data object, it will call the registered listeners once the event happens. The three arguments will contain these values:

attachToComponentMouseEvents

Registers this broadcaster to be notified for mouse events for the given components.

Broadcaster.attachToComponentMouseEvents(var componentIds, var callbackLevel, var optionalMetadata)


This registers the broadcaster to a list of components that will send out a message whenever the mouse callback is being triggered.

After you've defined this method, all functions must have the prototype

function mouseCallback(component, event)
{
	
}

where component is a reference to the actual script component object that triggered the mouse callback and event is a JSON object that is identical to the one you know and love from ScriptPanel.setMouseCallback() .


attachToComponentProperties

Registers this broadcaster to be called when one of the properties of the given components change.

Broadcaster.attachToComponentProperties(var componentIds, var propertyIds, var optionalMetadata)


Calling this function will attach the broadcaster to a list of components and properties and will notify its listeners everytime that one of the properties change.

After you've defined this method, all functions must have the prototype

function propertyCallback(component, id, value)
{
	
}

where component is a reference to the actual script component object that triggered the mouse callback, id is the property string and value the updated value.

attachToComponentValue

Registers this broadcaster to be called when the value of the given components change.

Broadcaster.attachToComponentValue(var componentIds, var optionalMetadata)


Calling this function will attach the broadcaster to a list of components and properties and will notify its listeners everytime that one of the properties change.

After you've defined this method, all functions must have the prototype

function valueCallback(component, value)
{
	
}

where component is a reference to the actual script component object that triggered the mouse callback, and value the updated value.

Note how the function signature is identical to the function parameters you can pass into setControlCallback() which makes it super easy to migrate from a normal control callback to a broadcaster based system.

attachToComponentVisibility

Registers this broadcaster to be called when the visibility of one of the components (or one of its parent component) changes.

Broadcaster.attachToComponentVisibility(var componentIds, var optionalMetadata)


This function is similar to attachToComponentProperties used with the visible property, but with the additional feature that it also takes the parent's visibility into account. So if you want to react on something being actually shown on the interface it might be a more stable solution if you have a deep component hierarchy.


attachToContextMenu

Registers this broadcaster to be notified when a context menu item from the given components was selected.

Broadcaster.attachToContextMenu(var componentIds, var stateFunction, var itemList, var optionalMetadata, var useLeftClick)


This function can be used to attach the broadcaster to any component and show a context menu when the user clicks on it with the right button (or double-tap on the trackpad / Ctrl+Click on macOS). It expects these 4 parameters:

  1. a single component or list of components (either a String with the component ID or existing script references)
  2. An array with Strings containing the popup menu items with a pseudo markdown syntax and the {DYNAMIC} wildcard to create dynamic item texts
  3. A state function that expects two arguments (type , index ) and allows changing the active state / disable items or use dynamic text values. The first argument is always one of three strings (text , enabled or active and indicates which state it wants to know). Be aware that this function is called synchronously on the UI thread for every item so keep it simple.
  4. Optional metadata (either JSON or String).

This function does not override any existing behaviour for right clicks, so you might want to eg. disable MIDI learn for components that you attach to this broadcaster.

After you've attached the broadcaster to the context menu of a component, right clicking will create a popup menu (using the component's LookAndFeel Customization ) and then send a message to its registered listeners with the clicked component as argument and the selected index (zero based).

Example

This example snippet registers a context menu to a button and allows setting the value and toggling the width of the button using two popup menu items.

// We'll attach the context menu to this button
const var Button1 = Content.addButton("Button1", 0, 0);
Button1.set("enableMidiLearn", false); // we don't want this to popup too...

/** Let's define a broadcaster with two arguments. */
const var bc = Engine.createBroadcaster({
	"id": "ContextMenu Broadcaster",
	"args": ["component", "selectedIndex"]
});

/** This defines a few items using a markdown-like syntax. */
const var POPUP_MENU_ITEMS = [
  "**Set Value / Properties**", // A header
  "Value is active",			// the first item
  "Set to {DYNAMIC}",			// the second item with a dynamic text
  "___",						// a horizontal separator
  "~~This is always off~~"		// an item that is always disabled
];


inline function popupStateFunction(type, index)
{
	// The this object of this function will always
	// point to the component that was clicked (in our
	// case it's always Button1).
	Console.assertEqual(this, Button1);

	local getEnableState = type == "enabled";
	local getTextValue = type == "text";
	local getActiveState = type == "active";
	
	if(getEnableState) // We don't want to disable any item
		return true; // so let's return true...
		
	if(getTextValue)
	{
		// This function is only called with items
		// that specify the `{DYNAMIC}` wildcard
		// in this case the second item with the index 1
		Console.assertEqual(value, 1);
		
		// Now we can return whatever text we want to show
		// and this is evaluated each time before the popup
		// is shown
		return this.get("width") > 150 ? "small" : "wide";
	}
	
	if(getActiveState)
	{
		// Now we can decide whether the popup menu item
		// should be displayed as active or not

		// the first item checks whether the button is active
		if(index == 0)
			return this.getValue();
			
		// the second item checks whether its wide or not
		if(index == 1)
			return this.get("width") > 150;
	}
};

/** Now we can use the item list and the state function to attach the broadcaster
   to the context menu of the button (you can attach it to multiple components by
   passing in a list. */
bc.attachToContextMenu("Button1", popupStateFunction, POPUP_MENU_ITEMS, "Context Menu");


/** This callback will be executed whenever a popup menu item was selected. */
bc.addListener(Button1, "Menu callback", function(component, index)
{
	// the this object will point to the component
	Console.assertEqual(component, this);

	if(index == 0)
	{
		this.setValue(!component.getValue());
		this.changed();
	}
	if(index == 1)
	{
		this.set("width", component.get("width") > 150 ? 100 : 200);
	}
});


attachToEqEvents

Registers this broadcaster to be notified about changes to the EQ (adding / removing / selecting filter bands).

Broadcaster.attachToEqEvents(var moduleIds, var eventTypes, var optionalMetadata)


The parametric EQ in HISE has a dynamic amount of EQ bands which can be addressed using the math formula

attributeIndex = attributeType + bandIndex * bandOffset

Adding & Removing bands however cannot be queried by the standard HISE parameter system, but you can use this attachment type to be notified whenever a Band is added / removed.

Once a broadcaster is attached to EQ events, it will fire its callbacks with three parameters:

function(eventType, value)
{
   // eventType is one of the strings that define the event
   // value is a context dependent value (eg. the band index at selection)...
}


attachToInterfaceSize

Registers the broadcaster to be notified when the interface size changes.

Broadcaster.attachToInterfaceSize(var optionalMetadata)


This will call registered functions when you change the interface size using Content.setHeight() or Content.setWidth() (and once with the initial value from Content.makeFrontInterface() ).

Note that this will not be triggered when the scale factor of the UI changes.

attachToModuleParameter

Registers this broadcaster to be notified when a module parameter changes. Edit on GitHub

Broadcaster.attachToModuleParameter(var moduleIds, var parameterIds, var optionalMetadata)



attachToNonRealtimeChange

Attaches this broadcaster to receive realtime / nonrealtime render change events. Edit on GitHub

Broadcaster.attachToNonRealtimeChange(var optionalMetadata)



attachToOtherBroadcaster

Attaches this broadcaster to another broadcaster(s) to forward the messages. Edit on GitHub

Broadcaster.attachToOtherBroadcaster(var otherBroadcaster, var argTransformFunction, bool async, var optionalMetadata)



attachToProcessingSpecs

Attaches this broadcaster to changes of the audio processing specs (samplerate / buffer size). Edit on GitHub

Broadcaster.attachToProcessingSpecs(var optionalMetadata)



attachToRadioGroup

Registers this broadcaster to be notified when a button of a radio group is clicked.

Broadcaster.attachToRadioGroup(int radioGroupIndex, var optionalMetadata)


If you want the broadcaster to listen to a list of buttons that are grouped into a exclusive radio group, you can use this method. The radioGroupIndex must be the integer value that you've assigned as radioGroup property to the buttons.

It will automatically scan all components and find the ones that are using this group index and then send out a message to all listeners when one of the buttons is clicked.

In order for this to work, the Broadcaster needs to have a single argument defined as args property which will contain the index of the clicked button.

const var bc = Engine.createBroadcaster({
	"id": "My Radio Watcher",
	"colour": -1,
	"args": ["buttonIndex"]
});

Be aware that this index is using the same order as the component list shows.

Also this attached mode is the only mode that is using a "bidirectional" communication. This means that if you send a broadcaster message using sendMessage() or the assignment operator, it will also change the currently active button in the radio group.

Example: Page Handling

One of the most practical use cases for this function is the page handling logic which will show and hide panels when you click on the corresponding buttons

Please note how the entire logic and functionality is represented in the broadcaster map from the state of the buttons to the visible property of the panels.

const var bc = Engine.createBroadcaster({
	"id": "My Page Handler",
	"colour": -1,
	"comment": "This broadcaster will handle the page logic",
	"args": ["pageIndex"]
});

// Just a dummy function that 
inline function addRadioButton(i)
{
	local b = Content.addButton("RadioButton " + (i+1), 0, i * 30);
	b.set("radioGroup", 90);
	b.set("saveInPreset", false);
	
	local p = Content.addPanel("Page" + (i+1), 150 + i* 100, 0);
	p.set("visible", false);
	return p;
}

// This array will hold 4 panels
const var Pages = [];

// Create 4 radio buttons and 4 panels
for(i = 0; i < 4; i++)
	Pages.push(addRadioButton(i));

bc.attachToRadioGroup(90, "Button Group");

const var PageList = []; //

bc.addComponentPropertyListener(Pages, "visible", "Show Panels", function(indexInList, buttonIndex)
{
	return indexInList == buttonIndex;
});

// Show the first page
bc.pageIndex = 0;


attachToRoutingMatrix

Attaches this broadcaster to a routing matrix and listens for changes.

Broadcaster.attachToRoutingMatrix(var moduleIds, var optionalMetadata)


This function attaches the broadcaster to a routing matrix of one or more processors to be notified whenever the routing changes (so either the channel configuration or the amount of channels). In order to use this function, the broadcaster must have two arguments, the first will be the processor ID and the second one a Routingmatrix object that you can query in your callback.

Be aware that you must not call any functions in a listener callback that itself causes the routing matrix to change or you will end up with an infinite loop!

const var bc = Engine.createBroadcaster({
	"id": "router",
	"args": ["id", "matrix"]
});

bc.attachToRoutingMatrix("Sine Wave Generator1", "script matrix");

bc.addListener("", "dudel", function(id, matrix)
{
	// this just prints out where the sine wave generator is mapped
	Console.print(trace(matrix.getDestinationChannelForSource([0, 1])));
});


attachToSampleMap

Attaches the broadcaster to events of a samplemap (loading, changing, adding samples).

Broadcaster.attachToSampleMap(var samplerIds, var eventTypes, var optionalMetadata)


This will register the broadcaster to be notified whenever a sample map is changed. There are three event types that can be selected. A broadcaster that is supposed to be attached to sample maps needs 3 arguments and will fire callbacks with these arguments:

const var b = Engine.createBroadcaster({
	id: "sampleListener",
	args: ["eventType", "samplerId", "data"]
});

b.attachToSampleMap("Sampler1", "SampleMapChanged", "");

b.addListener("", "funky", function(eventType, samplerId, data)
{
	Console.print(data);
});

Note that using a broadcaster for listening to sample map changes is the best practice going forward and replaces the usage of the ScriptPanel.setLoadingCallback() function for this task.

These are the different event types:

Type Description data argument
SampleMapLoaded Whenever a sample map is loaded (or cleared). the reference string as it goes into Sampler.loadSampleMap()
SamplesAddedOrRemoved Whenever a sample was added to (or removed from) the current samplemap the current number of samples.
SampleChanged Whenever a sample property has changed A JSON object with the sample information (see below).

The eventTypes argument will expect either the Type string or an array with multiple type strings from the table above with all event types that the broadcaster should listen to.

The samplerIds argument should be either a String of the sampler ID (or an array of strings for every sampler)
that you want to listen to.

Sample changes

If you want to listen to sample property changes (like changing the sample start or the low velocity), the values that you should pass into the eventTypes argument is not the "SamplesChanged" string, but one of the constants of the Sampler API object.

In this mode, the data argument will be a JSON object with these properties:

b.attachToSampleMap("Sampler1", [ Sampler.LoKey, Sampler.HiKey ], "");

b.addListener("", "funky", function(eventType, samplerId, data)
{
	if(data.id == Sampler.LoKey)
	{
		Console.print("Changed low key to " + data.value);
	}
	if(data.id == Sampler.HiKey)
	{
		Console.print("Changed high key to " + data.value);
	}
});


callWithDelay

Calls a function after a short period of time. This is exclusive, so if you pass in a new function while another is pending, the first will be replaced. Edit on GitHub

Broadcaster.callWithDelay(int delayInMilliseconds, var argArray, var function)



isBypassed

Checks if the broadcaster is bypassed. Edit on GitHub

Broadcaster.isBypassed()



refreshContextMenuState

If this broadcaster is attached to a context menu, calling this method will update the states for the menu items. Edit on GitHub

Broadcaster.refreshContextMenuState()



removeAllListeners

Removes all listeners. Edit on GitHub

Broadcaster.removeAllListeners()



removeAllSources

Removes all sources. Edit on GitHub

Broadcaster.removeAllSources()



removeListener

Removes the listener that was assigned with the given object.

Broadcaster.removeListener(var idFromMetadata)


This removes the listener from the list so that it will not be notified anymore. The parameter must be the exact same thing you've used in the addListener() function, so if you're using a JSON object, you need to use the exact same object (and not a clone with the same properties).

If you just want to temporarily deactivate a listener, you can do so by pressing the bypass button in the Broadcaster Controller. This is helpful for debugging

removeSource

Removes the source with the given metadata. Edit on GitHub

Broadcaster.removeSource(var metadata)



resendLastMessage

Resends the current state. Edit on GitHub

Broadcaster.resendLastMessage(var isSync)



reset

Resets the state.

Broadcaster.reset()


This resets the value to the default values passed into the constructor and sends a message to all listeners.

sendAsyncMessage

Sends an asynchronous message to all listeners. the length of args must match the default value list. Edit on GitHub

Broadcaster.sendAsyncMessage(var args)



sendMessage

deprecated function (use sendSyncMessage / sendAsyncMessage instead).

Broadcaster.sendMessage(var args, bool isSync)


This sends a message to all registered (and enabled) listeners. The first argument must be either

  1. An array, if the listener functions have multiple parameters. Then it will distribute the array elements to the function parameters.
  2. A single value if the registered functions and the default values have only one parameter.

The second parameter will control whether the message is being sent out synchronously or if it should be deferred and called a little bit later. This might be helpful if you want to coallascate calls to sendMessage so that it doesn't hammer the queue.

Be aware that it only sends the message to the listeners if any of the values is different than before.

Note: this function is deprecated and replaced by two other functions, sendAsyncMessage() and sendSyncMessage() . The rationale behind this is that the boolean parameter wasn't clear enough to indicate whether a message is being sent out synchronously or not (true means sync or async?). So using this message will write an error message to the console but the functionality keeps working so you can migrate it more easily.


sendMessageWithDelay

Sends a message to all listeners with a delay.

Broadcaster.sendMessageWithDelay(var args, int delayInMilliseconds)


This will send out a message after a certain delay so it might come in handy in scenarios where you previously had to drag a timer around.

Be aware that if you call this method while a callback is pending, it will override the value to be send out and restart the timer, so the first message might get lost.

sendSyncMessage

Sends a synchronous message to all listeners (same as the dot assignment operator). the length of args must match the default value list. Edit on GitHub

Broadcaster.sendSyncMessage(var args)



setBypassed

Deactivates the broadcaster so that it will not send messages. If sendMessageIfEnabled is true, it will send the last value when unbypassed. Edit on GitHub

Broadcaster.setBypassed(bool shouldBeBypassed, bool sendMessageIfEnabled, bool async)



setEnableQueue

If this is enabled, the broadcaster will keep an internal queue of all messages and will guarantee to send them all.

Broadcaster.setEnableQueue(bool shouldUseQueue)


When the broadcaster is used asynchronously, it will always just send a message for the latest state, so

bc.sendMessage(0, false);
bc.sendMessage(1, false);

in this example the message with the value 0 will never reach its listeners. This is the default value in order to avoid unnecessary calls to the listeners, however there are a few occasions where you need to guarantee that every message gets sent to the listeners. By enabling the queue, it will keep a list of all pending messages and sends out every value to its listeners.

If you attach certain event sources to a broadcaster, they will automatically switch to queue mode (eg. complex data events).

You can check the state of this value by the icon on the broadcaster map. If this icon appears:

the broadcaster is running in queued mode.

setForceSynchronousExecution

Forces every message to be sent synchronously.

Broadcaster.setForceSynchronousExecution(bool shouldExecuteSynchronously)


This function will enforce synchronous execution of its callback independently of which function is called.

setRealtimeMode

Guarantees that the synchronous execution of the listener callbacks can be called from the audio thread. Edit on GitHub

Broadcaster.setRealtimeMode(bool enableRealTimeMode)



setReplaceThisReference

This will control whether the this reference for the listener function will be replaced with the object passed into addListener . Edit on GitHub

Broadcaster.setReplaceThisReference(bool shouldReplaceThisReference)



setSendMessageForUndefinedArgs

Forces the broadcaster to also send a message when a parameter is undefined.

Broadcaster.setSendMessageForUndefinedArgs(bool shouldSendWhenUndefined)


By default, the broadcaster will not send a message when one or more arguments are undefined. This prevents wrong initialisation calls and script errors when the arguments are passed into function calls.

If you want to disable that function and also send messages for undefined arguments, call this function to change that behaviour (but in that case make sure to check isDefined() before passing the parameters into function calls to avoid script errors).

If you create a broadcaster, all the arguments will have an undefined state until you send the first message.