There have been a few topics here discussing implementing an Application Programming Interface (API) but none have progressed too far. I have been working on this a bit lately and have a high-level proposal for the kind of approach and type of monitoring and control that we may want / need to implement. I have intentionally avoided considering the low-level technology to implement this because I don’t want the high-level design to be driven or constrained by preconceptions of specific technology.
The aim is to separate user interface from core functionality, providing a core accessible via the API and allowing different user interfaces to be designed. These may include the current GUI with 4 encoders, simple stomp boxes, web interfaces (like webconf), etc.
Here is the initial high-level proposal which I publish here for comment but should probably live somewhere (like GitHub). Please take a look if you are interested in using an API and comment on errors, omissions, etc. We will soon be looking at the low level technology to implement this. The API may replace CUIA and other access methods, e.g. direct OSC monitoring and control of mixer.
Zynthian API proposal
Zynthian-core is (will be) the module that provides core functionality for Zynthian. This includes managing audio and MIDI processing engines, signal routing, monitoring, etc. The API will provide control and monitoring of zynthian-core allowing different user interfaces to be connected.
The current implementation of Zynthian (2109) does not separate UI from core functionality. This will be refactored to separate zynthian-core from zynthian-ui (and other elements of ZynthianOS). An interim API may be implemented by wrapping 2109 zynthian-ui to present the API as if only zynthian-core is running. There is an overhead to this process which will give reduced performance.
High Level Design
A model-view-controller approach is proposed. Zynthian-core will contain a model that holds the current configuration and data. This is equivalent to the current snapshot, containing all data to describe the state of the system. It will also contain much of the controller, allowing the control of the model although some elements of the controller may be implemented in the user interface modules. Interaction with users will be implemented by the view which may be any user interface. The UI may also implement some controller elements to enhance functionality, e.g. add extra features not embedded within the controller / model. The API will provide Create/Read/Update/Delete (CRUD) type access to the model and functions of zynthian-core as well as real-time functions that do not fit the CRUD access model. The API will be extensible to allow addition of data without breaking the API and use major.minor version to indicate breaking / non-breaking changes.
Create - Clients may request elements be added to the model.
Read - Clients may request (almost) any data within the model. This may be polled and/or accessed via a subscription + publish mechanism to allow registration by API clients for data to be dynamically published to the client as the data changes. (These may be considered the pull and push mechanisms.) These two approaches allows for static or slow changing data to be requested / polled whilst more urgent or dynamic data to be published in a timely manner or for an event driven client. Not all data will be available to the subscription + publish model to avoid excessive code and overhead - not all data is required.
Update - Clients may request changes to data / states.
Delete - Clients may request elements be removed from the model.
There may be a permissions scheme applied for clients to allow / restrict some operations. This is not high priority but may prove advantageous, e.g. allowing tightly bound modules fuller access than remote control devices.
The specific technology will be chosen to suit requirements. This will consider overhead, latency, security, likely (forecast) clients, etc.
Client connections may be session or non-session based. It should be possible for a client to make a request without expecting a response. It should also be possible for a client to validate a request has succeeded. This may be via a session based connection, a data subscription or a poll.
Sessions - A session is a bi-directional connection between client and server. The client connects to the server and remains connected throughout the session. Requests made by the client can be responded to by the server over the connection. The server may know about current sessions hence be able to send dynamic data to connected clients.
Connectionless - Connectionless is a uni-directional transmission of data. A sender may send a request or data to a receiver. There may not be a concept of connection so any dynamically updated data may need a keep-alive and timeout mechanism to avoid dead clients being sent data. A client may subscribe for data then request a change and be notified of the change through the subscription publication. It will also be notified via the same mechanism for changes made by other clients or the core.
Choice of session or connectionless based API will be considered with use cases and likely (forecast) clients. One or both may be adopted. The higher-level concepts of subscription + publish, etc. will be implemented and selected lower-level data transfer mechanism(s) will implement this.
The following is a non-exhaustive list of data that may be implemented in the model / API access methods and real-time messages. The suffix in square braces identifies how the data may be manipulated by the API [CRUD].
Mixer - Multiple channels of audio mixed to a main stereo mix-bus
- Fader level per chain [-RU-]
- Balance/pan value per chain [-RU-]
- Mute state per chain [-RU-]
- Solo state per chain [-RU-]
- Mono per chain [-RU-]
- Peak programme meter level per chain per audio channel [-R–]
- Peak programme meter peak hold per chain per audio channel [-R–]
- Chain [CRUD] *Testing branch binds chains to mixer channels - may abstract this if more flexibility required otherwise may add:
- Audio [-R–] Boolean: True if audio chain hence enable audio mixer channel - feels wrong but is pragmatic reduction in dataset
Chain - Group of interconnected engines
- Quantity of chains [-R–]
- Name [-RU-]
- Index [-RU-] *This is currently inferred from MIDI channel - it may be advantageous to separate this
- MIDI Channel [-RU-] *This is a base design constraint that requires clone to extend - should we instead allow parallel MIDI engines within a chain and assign MIDI channel to engine?
- Note range [-RU-] *Should this be implemented at engine level?
- Transpose [-RU-] *Should this be implemented at engine level?
- Engines within chain [CRUD] *Need to represent the map of how engines are interconnected - currently done by routing lookup but may benefit from a logical map (note that some devices place constraints on such a map to simplify design / UI / UX)
Engine - Instance of an engine class
- Engine class [-R–]
- MIDI control mapping - MIDI learn [CRUD]
- Selected preset [-RU-]
- Preset modified [-R–] Boolean: True if any parameter differs from preset
- Parameter values [-RU-]
- Chain [-R–] *This is a reverse lookup or double linked data - might be implemented virtually rather than maintain redundant data
Engine Class - Types of audio / MIDI processors / generators
- Available engine classess [-R–] *List of available engine classes
- Name [-R–]
- Type [-R–]
- Nodes [CRUD] *List of inputs and outputs
- Banks which are groups of presets [CRUD]
- Presets which are engine configurations [CRUD] *Advantageous to present bank/preset relationship in both directions
- Parameters which are configuration elements of engines [-R–]
- Default value
- Range / permissible values
- Group (allow grouping parameters, e.g. ADSR within envelope group)
- Each instance also has associated MIDI control parameters:
- Range / mapped values
Routing Graph - graph of MIDI and audio routes
- Nodes [CRUD] - List of nodes in graph
- Type [-R–] Audio | MIDI
- I/O [-R–] Source | Destination
- Interconnects [CRUD] - List of connections between nodes
- Type [-R–] Audio | MIDI
- Source [-RU-] - source node
- Destination [-RU-] - destination node
- Nodes [CRUD] - List of nodes in graph
Presets - Saved configuration of an engine
- Parameter values [-RU-] *May be a delta from the default or a full set of parameter values in which case [CRUD] - TBC
- Name [-RU-]
- Favourite [-RU-] Boolean: True if a favourite - may be read on bulk to get list of favourites, optionally filtered by engine, tag, etc.
Snapshots - Full model data
- Available snapshots [CRUD] *List of snapshots but also methods to create, update and delete snapshots
- Name [-RU-]
- Data [-RU-] *Full state model
Physical UI - Switches, encoders, potentiometers, etc
- Quantity of switches [-R–]
- Quantity of Encoders [-R–]
- Quantity of Potentiometers [-R–]
- Switch value [-RU-] *Allow trigger of switch via API
- Encoder parameters [-RU-] *Allows core to present encoder as a data element
- Potentiometer parameters [-RU-] *Allows core to present encoder as a data element
Step Sequencer - Manipulation of patterns, sequences, etc.
- See zynseq.h for exposed methods that may be exposed via API
- Pattern [CRUD]
- Track [CRUD]
- Sequence [CRUD]
- Song [CRUD]
Real Time Messaging - Interface for low latency, real-time messages *Some other messages may need to be reduced latency, e.g. mixer control and monitoring though maybe not real-time, i.e. there may be different levels of latency message type
- Send MIDI [----] *Ability to send a MIDI message to a defined set of MIDI destinations
- Register for MIDI events [CRUD] *Request MIDI messages are sent to client when received - filter on message type, value ranges, source, etc.
- Transport [-RU-]
System - Monitor and control the overall system
- Uptime [-R–]
- Errors [-RU-] *xruns / voltage / temperature since reset
- Restart core [–U-]
- Shutdown [–U-]
- Reboot [–U-]
- Panic [–U-] *All notes / all sounds
- Audio recording [CRUD]
- MIDI recording [CRUD]
GUI - Zynthian specific monitoring and control of GUI, extensible / optional to allow system specific implementation
- Reload MIDI config [–U-] *Currently loaded from file - may be replaced with direct control of config
- Reload key binding [–U-] *Currently loaded from file - may be replaced with direct control of config
- Last state action [-RU-]
- Selected screen [-RU-]
- Selected chain [-RU-]
[Edit] This text is updated to reflect discussion and decisions made below.