November 2021
Abstract
Manual about usage, installation, development and implementation of the Anklang digital synthesizer and music creation software.
This is the Anklang manual, Anklang is a music synthesis and composition program, released as Free Software under the GNU LGPL-2.1+ and the MPL-2.0.
The manual is structured into sections covering tutorial material, Howto descriptions, Man pages, file formats and conventions, development details with related considerations and an API reference.
It is written in Markdown and contributions are welcome, e.g. as pull requests or via the Anklang issue tracker.
NAME
anklang - Music composition and modular synthesis application
SYNOPSIS
anklang [OPTIONS] [FILES...]
DESCRIPTION
Anklang is a digital audio synthesis application for live creation and composition of music and other audio material. It is released as free software under the MPL-2.0 and includes plugins under the GNU LGPL-2.1+.
Anklang comes with synthesis devices which can be arranged in tracks and controlled via MIDI input devices or pre-programmed clips which contain MIDI notes.
The Anklang sound engine is a dedicated process which is controlled by a user interface based on web technologies that can be run in a special process (like electron) or modern browsers like firefox(1) or google-chrome(1).
OPTIONS
Anklang supports short and long options which start with two dashes ('-').
SEE ALSO
Technically, Anklang consists of a user interface front-end based on web technologies (HTML, SCSS, JS, Vue) and a synthesis engine backend written in C++. 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.
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 use Scope::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 or Class->shared_from_this()
wrapping.Class& from_json()
- return *shared_ptr<Class>
, throws on nullptr
. !!!to_json (const Class &v)
- return to_json<Class*>()
jsonvalue_as_string()
for debugging purposes.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 JsonapiTrigger/create (id); // JS->C++
void JsonapiTrigger/remove (id); // JS->C++
void JsonapiTrigger/_<id> ([...]); // C++->JS
void JsonapiTrigger/killed (id); // C++->JS
shared_ptr<CbFunc>
with using CbFunc = std::function<void (ValueS)>;
Building on Jsonipc, a small serializaiton 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 an persistent relative object link for project data storage.
The Vue components used in Anklang are generally composed of a single file that provides:
The Vue component object is implemented as part of (d). We often use <canvas>
elements for Anklang specific displays, and Vue canvas handling comes with certain caveats:
Use of the Util.vue_mixins.dom_updates
mixin (now default) allows to trigger the dom_update()
component method for $forceUpdate()
invocations and related events.
A methods: { dom_update() {}, }
component entry should be provided that triggers the actual canvas rendering logic.
Using a document.fonts.ready
promise, Anklang re-renders all Vue components via $forceUpdate()
once all webfonts have been loaded, <canvas>
elements containing text usually need to re-render themselves in this case.
Envue components are created to simplify some of the oddities of dealing with Vue-3 components. The function Envue.Component.vue_export
creates a Vue component definition, so that the Vue component instance ($vm
) is tied to an Envue.Component
instance ($object
). Notes:
$object.$vm
.$vm.$object
.$vm.*
fields e.g. from within a <template/>
definition are forwarded to access $object.*
fields.observable_from_getters()
.Vue uses a template compiler to construct a render() function from HTML <template/>
strings. The Javascript expressions in templates are sandboxed and limited in scope, but may refer to Vue component properties that are exposed through hasOwnProperty()
. In order to support Envue instance methods and fields in template expressions, all members present after Envue construction are forwarded into the Vue component.
A modal b-dialog that displays version information about Anklang.
Events:
This element provides a choice popup to choose from a set of options. It supports the Vue v-model protocol by emitting an input
event on value changes and accepting inputs via the value
prop.
Props:
[ { icon, label, blurb }... ]
Events:
A Vue template to display a list of Ase.Clip instances.
Props:
A Vue template to display a small view of Ase.Clip.
Props:
Vue template to display a color picker popup.
Props:
data:
A mechanism to display data-bubble="" tooltip popups on mouse hover.
Editor for audio signal devices.
Props:
Panel for editing of devices.
Props:
A dialog that captures focus and provides a modal shield that dims everything else. A close event is emitted on clicks outside of the dialog area, if Escape is pressed or the default Close button is actiavted.
Props:
v-model:shown
bindings.
Events:
false
is emitted when the "Close" button activated.
Slots:
CSS Classes:
b-editable - Display an editable span.
Methods:
Props:
true
to automatically select all editable text.Events:
A field-editor for integer or floating point number ranges. The input value
will be constrained to take on an amount between min
and max
inclusively.
Properties:
value
can take on.
value
can take on.
true
, numbers are constrained to integer values.
Events:
A field-editor for object input. A copy of the input value is edited, update notifications are provided via an input
event.
Properties:
input
event notifications.
Events:
A field-editor for picklist input.
Properties:
Events:
A field-editor switch to change between on and off.
Properties:
Events:
A field-editor for text input.
Properties:
Events:
A modal b-dialog that allows file and directory selections.
Properties:
Events:
A browser view for the selection of path components.
Properties:
Emits
Vue template to display a horizontal scrollbar.
Props:
This element displays icons from various icon fonts. Note, to style the color of icon font symbols, simply apply the color
CSS property to this element (styling fill
as for SVG elements is not needed).
Props:
fa
, bc
, mi
, uc
.
This element provides a knob for scalar inputs. It supports the Vue v-model protocol by emitting an input
event on value changes and accepting inputs via the value
prop.
Props:
-1…+1
.
0…+1
if bidir is false
.
Events:
Implementation Notes
The knob is rendered based on an SVG drawing, which is arranged in such a way that adding rotational transforms to the SVG elements is sufficient to display varying knob levels. Chrome cannot render individual SVG nodes into seperate layers (GPU textures) so utilizing GPU acceleration requires splitting the original SVG into several SVG layers, each of which can be utilized as a seperate GPU texture with the CSS setting will-change: transform
.
Indicator for adding or dropping new UI elements.
Noteboard to post notifications for end users.
A Vue template to display a list of Ase.Part instances.
Props:
A Vue template to display a thumbnail for a Ase.Part.
Props:
A Vue template to display notes from a MIDI event source as a piano roll.
Props:
Data:
A container holding the play and seek controls for a Ase.song.
Props:
b-shell.project
.
b-positionview - Display of the project transport position pointer and related information
Props:
A modal b-dialog to edit preferences.
Events:
A property group is a collection of properties.
Properties:
A property input element, usually for numbers, toggles or menus.
Properties:
Shell for editing and display of a Ase.Project.
Props:
Area for the display of status messages and UI configuration.
This element provides a toggle button for boolean inputs. It supports the Vue v-model protocol by emitting an input
event on value changes and accepting inputs via the value
prop.
Props:
true
or false
.
Events:
A container for vertical display of Ase.Track instances.
Props:
A Vue template to display a project's Ase.Track.
Props:
An element representing an entry of a B-TREESELECTOR, which allows selections.
A container that displays a tree and allows selections.
Props:
Events:
Envue
.ComponentComponent base class for wrapping Vue components.
Component
(
vm)
this.$vm
point to the Vue component, and $vm.$object
point to this
.
update
()
observable_from_getters
(
tmpl)
$watch
(
args)
vue_export
(
vue_object)
[static]
Envue
.forward_access
(
vm, classinstance, ignores)
vm
to access fields on classinstance
.
Envue
.vue_export_from_class
(
Class, vue_object)
Class
instance.
…
vuechildren
()
[static]
$children
(and $vue_parent
) on every component.
autodataattrs
()
[static]
$attrs['data-*']
to $el
.
dom_updates
()
[static]
Vue mixin to provide DOM handling hooks. This mixin adds instance method callbacks to handle dynamic DOM changes such as drawing into a <canvas/>
. Reactive callback methods have their data dependencies tracked, so future changes to data dependencies of reactive methods will queue future updates. However reactive dependency tracking only works for non-async methods.
this.$el
has been createdthis.$el
has been reassigned or changed. Note, may also be called for v-if="false"
cases.this.$el
and after Vue component updates. Dependency changes result in this.$forceUpdate()
.dom_queue_draw()
. Dependency changes result in this.dom_queue_draw()
.this.dom_draw()
to be called during the next animation frame.this.$el
is removed.Util.
ResizeObserver
Util.
clone_descriptors
Util.
KeyCode
Util
.now
()
Util
.debounce
(
callback, options)
Yield a wrapper function for callback
that throttles invocations. Regardless of the frequency of calls to the returned wrapper, callback
will only be called once per requestAnimationFrame()
cycle, or after milliseconds
. The return value of the wrapper functions is the value of the last callback invocation. A cancel()
method can be called on the returned wrapper to cancel the next pending callback
invocation. Options:
wait
- number of milliseconds to pass until callback
may be called.restart
- always restart the timer once the wrapper is called.immediate
- immediately invoke callback
and then start the timeout period.Util
.capture_event
(
eventname, callback)
eventname
with a single callback exclusively
Util
.vue_component
(
element)
Util
.envue_object
(
element)
$object
from element or its ancestors
Util
.drag_event
(
event)
drag_event (event)
handling on a Vue component's element, use @pointerdown="Util.drag_event"
Util
.unrequest_pointer_lock
(
element)
element
if any and ensure it does not get a pointer lock granted unless request_pointer_lock() is called on it again.
Util
.has_pointer_lock
(
element)
Check if element
has a (pending) pointer lock Return:
element
has the pointer lock;Util
.request_pointer_lock
(
element)
element
and track its state Use this function to maintain pointer locks to avoid stuck locks that can get granted after exitPointerLock() has been called.
Util
.vm_scope_selector
(
vm)
Util
.vm_scope_style
(
vm, css)
css
to Vue instance vm
, use vm_scope_selector() for the vm
CSS scope
Util
.assign_forin
(
target, source)
source
and assign to `target
Util
.assign_forof
(
target, source)
source
and assign to `target
Util
.array_remove
(
array, item)
item
from array
Util
.array_index_equals
(
array, item)
array
index of element that equals item
Util
.map_from_kvpairs
(
kvarray)
kvarray
Util
.range
(
bound, end, step)
bound
[ if one arg is given or [bound
..end
[ by incrementing step
.
Util
.copy_recursively
(
src)
src
Util
.equals_recursively
(
a, b)
a == b
, recursively if the arguments are of type Array or Object
Util
.clamp
(
x, min, max)
Util
.fwdprovide
(
injectname, keys)
Util
.hyphenate
(
string)
Util
.weakoid
(
object)
Util
.join_classes
(
args)
args
.
Util
.object_zip
(
obj, keys, values)
obj[k] = v
for all k of keys, v of values
.
Util
.object_await_values
(
obj)
Util
.extend_property
(
prop, disconnector)
.value_
updates, a disconnector
function must be provided as second argument, to handle disconnection of property change notifications once the property is not needed anymore.
Util
.promise_state
(
p)
p
state as one of: 'pending', 'fulfilled', 'rejected'
Util
.VueifyObject
(
object, vue_options)
data
object. Properties with a getter (and possibly setter) are turned into Vue computed
properties, methods are carried over as methods
on the Vue() instance.
Util
.fnv1a_hash
(
str)
Util
.split_comma
(
str)
Util
.parse_hex_color
(
colorstr)
Util
.parse_hex_luminosity
(
colorstr)
Util
.parse_hex_brightness
(
colorstr)
Util
.parse_hex_pgrey
(
colorstr)
Util
.parse_hex_average
(
colorstr)
Util
.parse_colors
(
colorstr)
Util
.compute_style_properties
(
el, obj)
obj
resolved against the style of el
Util
.inside_display_none
(
element)
element
or any parentElement has display:none
Util
.is_displayed
(
element)
element
is displayed (has width/height assigned)
Util
.wheel_delta
(
ev)
{x,y}
with negative values pointing LEFT/UP and positive values RIGHT/DOWN respectively. For zoom step interpretation, the x/y pixel values should be reduced via Math.sign()
. For scales the pixel values might feel more natural, because while Firefox tends to increase the number of events with increasing wheel distance, Chromium tends to accumulate and send fewer events with higher values instead.
Util
.wheel2scrollbars
(
event, refs, scrollbars)
event
to call scrollBy() on refs[scrollbars...]
.
Util
.list_focusables
(
element)
element
or the document.
Util
.is_nav_input
(
element)
element
has inner input navigation
Util
.is_button_input
(
element)
element
is button-like input
Util
.element_midpoint
(
element)
Util
.push_focus_root
(
element, escapecb)
element
and its descendants
Util
.remove_focus_root
(
element)
element
previously installed via push_focus_root()
Util
.keydown_move_focus
(
event)
keydown
events
Util
.move_focus
(
dir, subfocus)
Util
.forget_focus
(
element)
element
Util
.setup_shield_element
(
shield, containee, closer)
containee
, call closer(event)
for pointer clicks on shield
or when ESCAPE
is pressed.
Util
.swallow_event
(
type, timeout)
type
events until timeout
has passed
Util
.popup_position
(
element, opts)
Util
.resize_canvas
(
canvas, csswidth, cssheight, fill_style)
Util
.dash_xto
(
ctx, x, y, w, d)
w
with dashes d
Util
.hstippleRect
(
ctx, x, y, width, height, stipple)
(x,y,width,height)
with pixel gaps of width stipple
Util
.roundRect
(
ctx, x, y, width, height, radius, fill, stroke)
Util
.gradient_apply_stops
(
grad, stoparray)
stoparray
to grad
, stoparray
is an array: [(offset,color)...]
Util
.linear_gradient_from
(
ctx, stoparray, x1, y1, x2, y2)
stoparray
Util
.canvas_ink_vspan
(
font_style, textish)
Util
.midi_label
(
numish)
align8
(
int)
Util
.telemetry_subscribe
(
fun, telemetryfields)
fun
for telemtry updates, returns unsubscribe handler.
Util
.telemetry_unsubscribe
(
telemetryobject)
fun
for telemtry updates, returns unsubscribe handler.
Util
.in_keyboard_click
()
Util
.keyboard_click
(
element, callclick)
Util
.in_array
(
element, array)
element
is contained in array
Util
.matches_forof
(
element, iteratable)
element
is found during for (... of iteratable)
Util
.element_text
(
element, filter)
Util
.dropdown
(
menu, event, options)
menu
using event.currentTarget
as origin.
Util
.clone_menu_icon
(
menu, uri, title)
uri
.
Util
.is_navigation_key_code
(
keycode)
Util
.keyboard_map_name
(
keyname)
Util
.display_keyname
(
keyname)
Util
.match_key_event
(
event, keyname)
Util
.add_hotkey
(
hotkey, callback, subtree_element)
Util
.remove_hotkey
(
hotkey, callback)
Util
.has_ancestor
(
element, ancestor)
ancestor
is an ancestor or element
Util
.create_note
(
text, timeout)
Util
.markdown_to_html
(
element, markdown_text)
element.innerHTML
from markdown_text
Util
.assign_async_cleanup
(
map, key, cleaner)
map[key] = cleaner
, while awaiting and calling any previously existing cleanup function
Util
.observable_force_update
()
observable_from_getters()
result to force updates.
Util
.observable_from_getters
(
tmpl, predicate)
Create a reactive dict from the fields in tmpl
with async callbacks.
Once the resolved result from predicate()
changes and becomes true-ish, the getter()
of each field in tmpl
is called, resolved and assigned to the corresponding field in the observable binding returned from this function. Optionally, fields may provide a notify
setup handler to install a notification callback that re-invokes the getter
. A destructor can be returned from notify()
once resolved, that is executed during cleanup phases. The default
of each field in tmpl
may provide an initial value before getter
is called the first time and in case predicate()
becomes false-ish. The first argument to getter()
is a function that can be used to register cleanup code for the getter result.
const data = {
val: { getter: c => async_fetch(), notify: n => add_listener (n), },
};
dict = this.observable_from_getters (data, () => this.predicate());
// use dict.val
When the n()
callback is called, a new getter call is scheduled. A handler can be registered with c (cleanup);
to cleanup resources left over from an async_fetch()
call.
Util
.tmplstr
(
a, e)
Util
.strpad
(
string, len, fill)
string
with fill
until its length is len
Util
.hash53
(
key, seed)
key
.
With four sample values \(V_{0}\), \(V_{1}\), \(V_{2}\) and \(V_{3}\), cubic interpolation approximates the curve segment connecting \(V_{1}\) and \(V_{2}\), by using the beginning and ending slope, the curvature and the rate of curvature change to construct a cubic polynomial.
The cubic polynomial starts out as:
Where \(0 <= x <= 1\), specifying the sample value of the curve segment between \(V_{1}\) and \(V_{2}\) to obtain.
To calculate the coefficients \(w_{0},…,w_{3}\), we set out the following conditions:
We obtain \(V_{1}'\) and \(V_{2}'\) from the respecting slope triangles:
With (6) → (4) and (7) → (5) we get:
The derivation of \(f(x)\) is:
From \(x=0\) → (1), i.e. (2), we obtain \(w_{0}\) and from \(x=0\) → (10), i.e. (8), we obtain \(w_{1}\). With \(w_{0}\) and \(w_{1}\) we can solve the linear equation system formed by (3) → (1) and (5) → (10) to obtain \(w_{2}\) and \(w_{3}\).
With the resulting coefficients:
\[ \begin{aligned} w_{0} &= V_{1} & &(initial\:value) \\ w_{1} &= \frac{V_{2} - V_{0}} {2} & &(initial\:slope) \\ w_{2} &= \frac{-V_{3} + 4 V_{2} - 5 V_{1} + 2 V_{0}} {2} & &(initial\:curvature) \\ w_{3} &= \frac{V_{3} - 3 V_{2} + 3 V_{1} - V_{0}} {2} & &(rate\:change\:of\:curvature) \end{aligned} \]
Reformulating (1) to involve just multiplications and additions (eliminating power), we get:
Based on \(V_{0},…,V_{3}\), \(w_{0},…,w_{3}\) and (13), we can now approximate all values of the curve segment between \(V_{1}\) and \(V_{2}\).
However, for practical resampling applications where only a specific precision is required, the number of points we need out of the curve segment can be reduced to a finite amount. Lets assume we require \(n\) equally spread values of the curve segment, then we can precalculate \(n\) sets of \(W_{0,…,3}[i]\), \(i=[0,…,n]\), coefficients to speed up the resampling calculation, trading memory for computational performance. With \(w_{0,…,3}\) in (1):
\[ \begin{alignedat} {2} f(x) \ &= & \frac{V_{3} - 3 V_{2} + 3 V_{1} - V_{0}} 2 x^3 \ + & \\ & & \frac{-V_{3} + 4 V_{2} - 5 V_{1} + 2 V_{0}} 2 x^2 \ + & \\ & & \frac{V_{2} - V_{0}} 2 x \ + & \\ & & V1 \ \ & \end{alignedat} \]
sorted for \(V_{0},…,V_{4}\), we have:
With (14) we can solve \(f(x)\) for all \(x = \frac i n\), where \(i = [0, 1, 2, …, n]\) by substituting \(g(i) = f(\frac i n)\) with
and using \(n\) precalculated coefficients \(W_{0,…,3}\) according to:
\[ \begin{alignedat}{4} m &= \frac i n \\ W_{3}[i] &=& 0.5 m^3 & - & 0.5 m^2 & & \\ W_{2}[i] &=& -1.5 m^3 & + & 2 m^2 & + 0.5 m & \\ W_{1}[i] &=& 1.5 m^3 & - & 2.5 m^2 & & + 1 \\ W_{0}[i] &=& -0.5 m^3 & + & m^2 & - 0.5 m & \end{alignedat} \]
We now need to setup \(W_{0,…,3}[0,…,n]\) only once, and are then able to obtain up to \(n\) approximation values of the curve segment between \(V_{1}\) and \(V_{2}\) with four multiplications and three additions using (15), given \(V_{0},…,V_{3}\).
There seems to be a lot of inconsistency in the behaviour of modifiers (shift and/or control) with regards to GUI operations like selections and drag and drop behaviour.
According to the Gtk+ implementation, modifiers relate to DND operations according to the following list:
Modifier | Operation | Note / X-Cursor | |
---|---|---|---|
none | → | copy | (else move (else link)) |
SHIFT |
→ | move | GDK_FLEUR |
CTRL |
→ | copy | GDK_PLUS , GDK_CROSS |
SHIFT+CTRL |
→ | link | GDK_UL_ANGLE |
Regarding selections, the following email provides a short summary:
From: Tim Janik <timj@gtk.org> To: Hacking Gnomes <Gnome-Hackers@gnome.org> Subject: modifiers for the second selection Message-ID: <Pine.LNX.4.21.0207111747190.12292-100000@rabbit.birnet.private> Date: Thu, 11 Jul 2002 18:10:52 +0200 (CEST) hi all, in the course of reimplementing drag-selection for a widget, i did a small survey of modifier behaviour in other (gnome/ gtk) programs and had to figure that there's no current standard behaviour to adhere to: for all applications, the first selection works as expected, i.e. press-drag-release selects the region (box) the mouse was draged over. also, starting a new selection without pressing any modifiers simply replaces the first one. differences occour when holding a modifier (shift or ctrl) when starting the second selection. Gimp: Shift upon button press: the new seleciton is added to the existing one Ctrl upon button press: the new selection is subtracted from the existing one Shift during drag: the selection area (box or circle) has fixed aspect ratio Ctrl during drag: the position of the initial button press serves as center of the selected box/circle, rather than the upper left corner Gnumeric: Shift upon button press: the first selection is resized Ctrl upon button press: the new seleciton is added to the existing one Abiword (selecting text regions): Shift upon button press: the first selection is resized Ctrl upon button press: triggers a compound (word) selection that replaces the first selection Mozilla (selecting text regions): Shift upon button press: the first selection is resized Nautilus: Shift or Ctrl upon buttn press: the new selection is added to or subtracted from the first selection, depending on whether the newly selected region was selected before. i.e. implementing XOR integration of the newly selected area into the first. i'm not pointing this out to start a flame war over what selection style is good or bad and i do realize that different applications have different needs (i.e. abiword does need compound selection, and the aspect-ratio/centering style for gimp wouldn't make too much sense for text), but i think for the benfit of the (new) users, there should me more consistency regarding modifier association with adding/subtracting/resizing/xoring to/from existing selections. --- ciaoTJ