Skip to content

Monitoring state changes of the engine

Igor Zinken edited this page Apr 28, 2020 · 9 revisions

This page concerns the monitoring of state messages of the audio engine. Additionally, it describes how to create and monitor your own messages in your custom application.

Default notifications / notifications.h

Out of the box, the MWEngine broadcasts several default messages, their definition resides in definitions/notifications.h. The contents of the enum are :

enum ids {
    SEQUENCER_POSITION_UPDATED,
    MARKER_POSITION_REACHED,
    SEQUENCER_TEMPO_UPDATED,
    RECORDING_STATE_UPDATED,
    BOUNCE_COMPLETE,

    STATUS_BRIDGE_CONNECTED,

    ERROR_HARDWARE_UNAVAILABLE,
    ERROR_THREAD_START
};

The meaning of these :

ERROR_HARDWARE_UNAVAILABLE fired when MWEngine cannot connect to audio hardware (fatal)
ERROR_THREAD_START         fired when MWEngine cannot start the rendering thread (fatal)
STATUS_BRIDGE_CONNECTED    fired when MWEngine connects to the native layer code through JNI
MARKER_POSITION_REACHED    fired when request Sequencer marker position has been reached

The following notifications also contain a payload value (see below) :

SEQUENCER_POSITION_UPDATED fired when Sequencer has advanced a step, payload describes
                           the precise buffer offset of the Sequencer when the notification fired
                           (as a value in the range of 0 - BUFFER_SIZE)
RECORDING_STATE_UPDATED    fired when a recording snippet of request size has been written
                           to the output folder, payload contains snippet number
BOUNCE_COMPLETE            fired when the offline bouncing of the Sequencer range has completed

As it is enumerated, it is important that your custom event-enums' index starts where the Notifications enum stops to avoid sending conflicting messages !

Depending on the context in which you use the engine (solely from C++ or through JNI in Java), receiving these messages occurs differently, though the interface remains the same, see below.

In C++ using Notifier / Observer

The engine uses a publish-subscribe pattern to transfer messages. A message consists basically of an integer value to act as its identifier (see enumeration notifications.h) and can optionally contain an integer value as the message data.

The same message id can be broadcast to multiple listeners, so you can easily separate concerns in your code by creating separate listeners for a single event.

observer.h

A listener is known as an Observer, which is basically a custom class that extends from observer.h. An Observer has the following methods which must be extended in your inheriting class:

void handleNotification( int aNotificationId );

Invoked by the Notifier whenever given message aNotificationId has been broadcast. The Observer will only receive notifications from notification identifiers it has subscribed to (see notifier.h). If it has subscribed to multiple notification identifiers, the body of the function can contain a switch statement to act according to the given notification identifier.

void handleNotification( int aNotificationId, int aNotificationValue );

The same as above, only this notification has a data value represented by given aNotificationValue (see notifier.h).

notifier.h

The notifier is a namespace providing static methods to register and broadcast messages to registered Observers :

void Notifier::registerObserver( int aNotificationId, Observer* aObserver );

Registers given aObserver to receive broadcasts of messages with given identifier aNotificationId. This Observer will not receive any broadcasts from any other notification identifiers, unless it is registered to listen to other specific identifiers.

void Notifier::unregisterObserver( int aNotificationId, Observer* aObserver );

Unregisters given aObserver from receiving broadcasts of messages with given identifier aNotificationId.

void Notifier::broadcast( int aNotificationId );

Broadcasts state message to all Observers that are registered to receive updates for given identifier aNotificationId.

void Notifier::broadcast( int aNotificationId, int aNotificationValue );

The same as above, but additionally sends a data value aNotificationValue to the registered Observers. You can use this value to create another level of possible states for the message (for instance to have aNotificationId describe an error and aNotificationValue describe what type of error has occurred, etc.)

In Java using MWEngine.IObserver

If you wish to receive notifications in Java (for instance to update a visual state to match the Sequencer position), be sure to have the following line inside global.h to ensure the Notifier is transferring messages to the Java layer:

#define USE_JNI

On the Java-side, the nl.igorski.mwengine.MWEngine-class acts as the bridge to receive notifications. However, you must implement your own Observer in Java, instead of registering Observers inside the Notifier on the native layer. All native layer broadcasts via the Notifier however still function as before, but are transmitted to the MWEngine-bridge.

Upon construction of the MWEngine, you pass in a custom implementation of the MWEngine.IObserver-interface.

This interface provides the same public methods as observer.h does :

void handleNotification( int aNotificationId );

and :

void handleNotification( int aNotificationId, int aNotificationValue );

However, there can be only one instance of an IObserver-implementation. Meaning you cannot register multiple Observers to listen to broadcasts of separate notification identifiers in the same as the C++ Observer can.

Instead you should use the IObserver-implementation as a delegate that reads the notification identifier in a switch statement, to then trigger the respective actions accordingly, for instance :

case ERROR:
    someClassReference.someErrorHandlingAction();
    break;

case POSITION_UPDATED:
    someOtherClassReference.somePositionHandlingAction();

etc.

translating notification identifiers to enumerations

In Java, enumerations are not treated as integers the same way as they are in C. To create a switch statement to identify the notification, you will need to retrieve the enumerated value by its index from the enumeration class. As this requires the creation of an array, this is an expensive operation to repeat, as such it makes sense to cache this array inside your IObserver-implementation like so:

final Notifications.ids[] _notificationEnums = Notifications.ids.values();

That way you can get the enumerated value for a notification like so:

_notificationEnums[ aNotificationId ]
Clone this wiki locally