ANKLANG Development Details
Technically, Anklang consists of a user interface front-end based on web technologies (HTML, DOM, CSS, JavaScript, Lit) and a synthesis engine backend written in C++.
ASE - Anklang synthesis engine
The ase/
subdirectory contains the C++ implementation of the AnklangSynthEngine
executable
which contains the core component for audio data processing and audio plugin handling.
It interfaces with the HTML DOM based user interface via an IPC layer with JSON messages that reflect the C++ API.
The synthesis engine can load various audio rendering plugins which are executed in audio rendering worker threads. The main synthesis engine thread coordinates synchronization and interafces between the engine and the UI via an IPC interface over a web-socket that uses remote method calls and event delivery marshalled as JSON messages.
Serialization
Building on Jsonipc, a small serialization framework provided by ase/serialize.hh is used to marshal values, structs, enums and classes to/from JSON. This is used to store preferences and project data. The intended usage is as follows:
std::string jsontext = Ase::json_stringify (somevalue);
bool success = Ase::json_parse (jsontext, somevalue);
// The JSON root will be of type 'object' if somevalue is a class instance
std::string s; // s contains:
s = json_stringify (true); // true
s = json_stringify (-0.17); // -0.17
s = json_stringify (32768); // 32768
s = json_stringify (Ase::Error::IO); // "Ase.Error.IO"
s = json_stringify (String ("STRing")); // "STRing"
s = json_stringify (ValueS ({ true, 5, "HI" })); // [true,5,"HI"]
s = json_stringify (ValueR ({ {"a", 1}, {"b", "B"} })); // {"a":1,"b":"B"}
In the above examples, Ase::Error::IO
can be serialized because it is registered as
Jsonipc::Enum<Ase::Error>
with its enum values. The same works for serializable
classes registered through Jsonipc::Serializable<SomeClass>
.
[_] Serialization of class instances will have to depend on the Scope/InstanceMap, so
instance pointers in copyable classes registered as Jsonipc::Serializable<>
can be
marshalled into a JsonValue (as {$id,$class}
pair), then be resolved into an
InstanceP stored in an Ase::Value and from there be marshalled into a persistent
relative object link for project data storage.
Jsonipc
Jsonipc is a header-only IPC layer that marshals C++ calls to JSON messages defined in jsonipc/jsonipc.hh. The needed registration code is very straight forward to write manually, but can also be auto-genrated by using jsonipc/cxxjip.py which parses the exported API using CastXML.
The Anklang API for remote method calls is defined in api.hh. Each class with its methods, struct with its fields and enum with its values is registered as a Jsonipc interface using conscise C++ code that utilizes templates to derive the needed type information.
The corresponding Javascript code to use api.hh
via
async
remote method calls is generated via Jsonipc::ClassPrinter::to_string()
by
AnklangSynthEngine --js-api
.
- [√]
shared_ptr<Class> from_json()
- lookup by id in InstanceMap or useScope::make_shared
for Serializable. - [√]
to_json (const shared_ptr<Class> &p)
- marshal Serializable or {id} from InstanceMap. - [√]
Class* from_json()
- return&*shared_ptr<Class>
- [√]
to_json (Class *r)
- supports Serializable orClass->shared_from_this()
wrapping. - [√]
Class& from_json()
- return*shared_ptr<Class>
, throws onnullptr
. !!! - [√]
to_json (const Class &v)
- returnto_json<Class*>()
- [√] No uses are made of copy-ctor implementations.
- [√] Need virtual ID serialization API on InstanceMap.
- [√] Add
jsonvalue_as_string()
for debugging purposes.
Callback Handling
Javascript can register/unregister remote Callbacks with create and remove. C++ sends events to inform about a remote Callback being called or unregistered killed.
void Jsonapi/Trigger/create (id); // JS->C++
void Jsonapi/Trigger/remove (id); // JS->C++
void Jsonapi/Trigger/_<id> ([...]); // C++->JS
void Jsonapi/Trigger/killed (id); // C++->JS