ANKLANG Development Details

The Anklang Project <anklang.testbit.org>

July 2022

Abstract
API documentation and development internals of the Anklang project.

1 ANKLANG Development Details

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.

1.1 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.

1.1.1 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    JsonapiTrigger/create (id);     // JS->C++
void    JsonapiTrigger/remove (id);     // JS->C++
void    JsonapiTrigger/_<id>  ([...]);  // C++->JS
void    JsonapiTrigger/killed (id);     // C++->JS
  • [_] Turn Jsonhook into shared_ptr<CbFunc> with using CbFunc = std::function<void (ValueS)>;
  • [_] Allow adding a Jsonhook Destroyer to e.g. disconnect Emittable::Connection
  • [_] Add Jsonhook.destroy
  • [_] Send hook ID to JS from Jsonhook impl with args for call
  • [_] Send "destroyed" to JS

1.2 Serialization

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.

2 Vue Component Reference

The Vue components used in Anklang are generally composed of a single file that provides:

  1. Brief documentation;
  2. CSS style information;
  3. Html layout;
  4. Assorted JS logic.

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:

  1. 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.

  2. A methods: { dom_update() {}, } component entry should be provided that triggers the actual canvas rendering logic.

  3. 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.

2.1 Envue components

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:

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.

2.1.1 B-ABOUTDIALOG

A modal b-dialog that displays version information about Anklang.

Events:

close
A close event is emitted once the "Close" button activated.

2.1.2 B-BUTTON-BAR

A Vue container for tight packing of buttons.

Slots:

slot
All contents passed into this element will be packed tightly and styled as buttons.

2.1.3 B-CHOICE

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:

value
Integer, the index of the choice value to be displayed.
choices
List of choices: [ { icon, label, blurb }... ]

Events:

update:value (value)
Value change notification event, the first argument is the new value.

2.1.4 B-CLIPLIST

A Vue template to display a list of Ase.Clip instances.

Props:

project
The project containing playback tracks.
track
The Track containing the clips to display.

2.1.5 B-CLIPVIEW

A Vue template to display a small view of Ase.Clip.

Props:

clip
The Ase.Clip to display.
tick
The Ase.Track tick position.
tickscale (read-only)
The pixel to PPQN ratio.

2.1.6 B-COLOR-PICKER

Vue template to display a color picker popup.

Props:

initialcolor
The initial color to display.

data:

color
The currently selected color.

2.1.7 B-CONTEXTMENU

A modal popup that displays contextmenu choices, see B-MENUITEM, B-MENUSEPARATOR. Using the popup() method, the menu can be popped up from the parent component, and setting up an onclick handler can be used to handle menuitem actions. Example:

<div @contextmenu.prevent="$refs.cmenu.popup">
  <b-contextmenu ref="cmenu" @click="menuactivation">...</b-contextmenu>
</div>

Props:

check: function (uri: string): bool
Callback property to check if a submenu item is enabled, the uri of the submenu is provided as argument.
xscale
Consider a wider area than the context menu width for popup positioning.
yscale
Consider a taller area than the context menu height for popup positioning.

Events:

click (uri: string)
Event signaling activation of a submenu item, the uri of the submenu is provided as argument.
close
Event signaling closing of a menu, regardless of whether menu item activation occoured or not.
startfocus
Auto focus a menu item upon popup, in the same way menu bar menus work.
keepmounted
Keep the menu and menu items mounted at all times, needed for map_kbd_hotkeys().

Methods:

popup (event, { origin, data-contextmenu })
Popup the contextmenu, the event coordinates are used for positioning, the origin is a
reference DOM element to use for drop-down positioning. The data-contextmenu element (or origin)
has the data-contextmenu=true attribute assigned during popup.
close()
Hide the contextmenu.
map_kbd_hotkeys (active)
Activate (deactivate) the kbd=... hotkeys specified in menu items.

2.1.8 B-DATABUBBLE

A mechanism to display data-bubble="" tooltip popups on mouse hover.

2.1.9 B-DEVICEEDITOR

Editor for audio signal devices.

Props:

device
Audio signal processing device.

2.1.10 B-DEVICEPANEL

Panel for editing of devices.

Props:

track
Container for the devices.

2.1.11 B-DIALOG

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:

shown
A boolean to control the visibility of the dialog, suitable for v-model:shown bindings.

Events:

update:shown
An update:shown event with value false is emitted when the "Close" button activated.

Slots:

header
The header slot can be used to hold the modal dialog title.
default
The default slot holds the main content.
footer
By default, the footer slot holds a Close button which emits the close event.

CSS Classes:

b-dialog
The b-dialog CSS class contains styling for the actual dialog contents.
b-dialog-modalshield
This CSS class contains styling for masking content under the dialog.

b-editable - Display an editable span.

Methods:

  • activate() - Show input field and start editing.

Props:

  • selectall - Set to true to automatically select all editable text.
  • clicks - Set to 1 or 2 to activate for single or double click.

Events:

  • change - Emitted when an edit ends successfully.

2.1.12 B-FED-NUMBER

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
Contains the number being edited.
min
The minimum amount that value can take on.
max
The maximum amount that value can take on.
step
A useful amount for stepwise increments.
allowfloat
Unless this setting is true, numbers are constrained to integer values.
readonly
Make this component non editable for the user.

Events:

input
This event is emitted whenever the value changes through user input or needs to be constrained.

2.1.13 B-FED-OBJECT

A field-editor for object input. A copy of the input value is edited, update notifications are provided via an input event.

Properties:

value
Object with properties to be edited.
readonly
Make this component non editable for the user.
debounce
Delay in milliseconds for input event notifications.

Events:

input
This event is emitted whenever the value changes through user input or needs to be constrained.

2.1.14 B-FED-PICKLIST

A field-editor for picklist input.

Properties:

value
Contains the picklist string being edited.
readonly
Make this component non editable for the user.

Events:

input
This event is emitted whenever the value changes through user input or needs to be constrained.

2.1.15 B-FED-SWITCH

A field-editor switch to change between on and off.

Properties:

value
Contains a boolean indicating whether the switch is on or off.
readonly
Make this component non editable for the user.

Events:

input
This event is emitted whenever the value changes through user input or needs to be constrained.

2.1.16 B-FILEDIALOG

A modal b-dialog that allows file and directory selections.

Properties:

title
The dialog title.
button
Title of the activation button.
cwd
Initial path to start out with.
filters
List of file type constraints.

Events:

select (path)
This event is emitted when a specific path is selected via clicks or focus activation.
close
A close event is emitted once the "Close" button activated.

2.1.17 B-FOLDERVIEW

A browser view for the selection of path components.

Properties:

entries
List of Ase.Entry objects.

Emits

click (entry)
This event is emitted whenever an entry was activated via keyboard, mouse or touch.
select (entry)
This event is emitted whenever an entry gets focus.

2.1.18 B-HSCROLLBAR

Vue template to display a horizontal scrollbar.

Props:

value
The current scrollbar value.

2.1.19 B-ICON

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:

iconclass
A CSS class to apply to this icon.
fa
The name of a "Fork Awesome" icon (compatible with "Font Awesome 4"), see the Fork Awesome Icons.
mi
The name of a "Material Icons" icon, see the Material Design Icons.
bc
The name of an "AnklangIcons Font" icon.
uc
A unicode character literal, see the Unicode Lists and Symbols.
ic
A prefixed variant of fa, bc, mi, uc.
nosize
Prevent the element from applying default size constraints.
fw
Apply fixed-width sizing.
lg
Make the icon 33% larger than its container.
hflip
Flip the icon horizontally.
vflip
Flip the icon vertically.

2.1.20 B-KNOB

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:

bidir
Boolean, flag indicating bidirectional inputs with value range -1…+1.
value
Float, the knob value to be displayed, the value range is 0…+1 if bidir is false.
format
String, format specification for popup bubbles, containinig a number for the peak amplitude.
label
String, text string for popup bubbles.
hscroll
Boolean, adjust value with horizontal scrolling (without dragging).
vscroll
Boolean, adjust value with vertical scrolling (without dragging).
width4height
Automatically determine width from externally specified height (default), otherwise determines height.

Events:

update:value (value)
Value change notification event, the first argument is the new value.
reset:value ()
Request to reset the value.

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.

2.1.21 B-MENUBAR

The main menu bar at the top of the window.

2.1.22 B-MENUITEM

A menuitem element to be used as a descendant of a B-CONTEXTMENU. The menuitem can be activated via keyboard focus or mouse click and will notify its B-CONTEXTMENU about the click and its uri, unless the @click.prevent modifier is used. If no uri is specified, the B-CONTEXTMENU will still be notified to be closed, unless $event.preventDefault() is called.

Props:

uri
Descriptor for this menuitem that is passed on to its B-CONTEXTMENU onclick.
kbd
Hotkey to display next to the menu item and to use for the context menu kbd map.
disabled
Boolean flag indicating disabled state.
fa, mi, bc, uc
Shorthands icon properties that are forwarded to a B-ICON used inside the menuitem.

Events:

click
Event emitted on keyboard/mouse activation, use preventDefault() to avoid closing the menu on clicks.

Slots:

default
All contents passed into this slot will be rendered as contents of this element.

2.1.23 B-MENUROW

Menuitems are packed horizontally inside a menurow.

Props:

noturn
Avoid turning the icon-label direction in menu items to be upside down.

Slots:

default
All contents passed into this slot will be rendered as contents of this element.

2.1.24 B-NOTEBOARD

Noteboard to post notifications for end users.

2.1.25 B-PARTLIST

A Vue template to display a list of Ase.Part instances.

Props:

project
The project containing playback tracks.
track
The Track containing the parts to display.

2.1.26 B-PARTTHUMB

A Vue template to display a thumbnail for a Ase.Part.

Props:

part
The Ase.Part to display.
tick
The Ase.Track tick position.

2.1.27 B-PIANO-ROLL

A Vue template to display notes from a MIDI event source as a piano roll.

Props:

msrc
The MIDI event source for editing and display.

Data:

hscrollbar
The horizontal scrollbar component, used to provide the virtual scrolling position for the notes canvas.

2.1.28 B-PLAYCONTROLS

A container holding the play and seek controls for a Ase.song.

Props:

project
Injected Ase.Project, using b-shell.project.

b-positionview - Display of the project transport position pointer and related information

Props:

  • project - The object providing playback API.

2.1.29 B-PREFERENCESDIALOG

A modal b-dialog to edit preferences.

Events:

close
A close event is emitted once the "Close" button activated.

2.1.30 B-PRO-GROUP

A property group is a collection of properties.

Properties:

name
Group name.
props
List of properties with cached information and layout rows.
readonly
Make this component non editable for the user.

2.1.31 B-PRO-INPUT

A property input element, usually for numbers, toggles or menus.

Properties:

value
Contains the input being edited.
readonly
Make this component non editable for the user.

2.1.32 B-SHELL

Shell for editing and display of a Ase.Project.

Props:

project
Implicit Ase.Project, using App.project().

2.1.33 B-STATUSBAR

Area for the display of status messages and UI configuration.

2.1.34 B-TOGGLE

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:

value
Boolean, the toggle value to be displayed, the values are true or false.
label
String, label to be displayed inside the toggle button.

Events:

update:value (value)
Value change notification event, the first argument is the new value.

2.1.35 B-TRACKLIST

A container for vertical display of Ase.Track instances.

Props:

project
The Ase.Project containing playback tracks.

2.1.36 B-TRACKVIEW

A Vue template to display a project's Ase.Track.

Props:

project
The Ase.project containing playback tracks.
track
The Ase.Track to display.

2.1.37 B-TREESELECTOR-ITEM

An element representing an entry of a B-TREESELECTOR, which allows selections.

2.1.38 B-TREESELECTOR

A container that displays a tree and allows selections.

Props:

defaultcollapse
Set default for collapsible entries.

Events:

close
A close event is emitted once the "Close" button activated.

2.2 Reference for ui/b/envue.js

2.2.1 Component class

class Envue.Component

Component base class for wrapping Vue components.

new Component (vm)
Let this.$vm point to the Vue component, and $vm.$object point to this.
update()
Force a Vue component update.
observable_from_getters (tmpl)
Wrapper for Util.observable_from_getters().
$watch (args)
Wrapper for Vue.$watch
vue_export (vue_object)   [static]
Create a Vue options API object from vue_object for SFC exports.

2.2.2 Envue Functions

Envue.forward_access (vm, classinstance, ignores)
Forward all accesses to fields on vm to access fields on classinstance.
Envue.vue_export_from_class (Class, vue_object)
Create a Vue options API object that proxies access to a newly created Class instance.

2.3 Reference for ui/util.js

2.3.1 vue_mixins class

class vue_mixins

vuechildren()   [static]
Provide $children (and $vue_parent) on every component.
autodataattrs()   [static]
Automatically add $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.

  • dom_create() - Called after this.$el has been created
  • dom_change() - Called after this.$el has been reassigned or changed. Note, may also be called for v-if="false" cases.
  • dom_update() - Reactive callback method, called with a valid this.$el and after Vue component updates. Dependency changes result in this.$forceUpdate().
  • dom_draw() - Reactive callback method, called during an animation frame, requested via dom_queue_draw(). Dependency changes result in this.dom_queue_draw().
  • dom_queue_draw() - Cause this.dom_draw() to be called during the next animation frame.
  • dom_destroy() - Callback method, called once this.$el is removed.

2.3.2 Util Constants

Util.ResizeObserver
Work around FireFox 68 having ResizeObserver disabled
Util.clone_descriptors
Copy PropertyDescriptors from source to target, optionally binding handlers against closure.

2.3.3 Util Functions

Util.now()
Retrieve current time in milliseconds.
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)
Process all events of type eventname with a single callback exclusively
Util.coalesced_events (event)
Expand pointer events into a list of possibly coalesced events.
Util.vue_component (element)
Get Vue component handle from element or its ancestors
Util.envue_object (element)
Get Envue $object from element or its ancestors
Util.drag_event (event)
Start drag_event (event) handling on a Vue component's element, use @pointerdown="Util.drag_event"
Util.unrequest_pointer_lock (element)
Clear (pending) pointer locks Clear an existing pointer lock on 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:

  • 2- if element has the pointer lock;
  • 1- if the pointer lock is pending;
  • 0- otherwise.
Util.request_pointer_lock (element)
Request a pointer lock on 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)
Retrieve CSS scope selector for vm_scope_style()
Util.vm_scope_style (vm, css)
Attach css to Vue instance vm, use vm_scope_selector() for the vm CSS scope
Util.assign_forin (target, source)
Loop over all properties in source and assign to `target
Util.assign_forof (target, source)
Loop over all elements of source and assign to `target
Util.array_remove (array, item)
Remove element item from array if present via indexOf
Util.array_index_equals (array, item)
Find array index of element that equals item
Util.map_from_kvpairs (kvarray)
Generate map by splitting the key=value pairs in kvarray
Util.range (bound, end, step)
Generate integers [0..bound[ if one arg is given or [bound..end[ by incrementing step.
Util.freeze_deep (object)
Freeze object and its properties
Util.copy_deep (src)
Create a new object that has the same properties and Array values as src
Util.equals_recursively (a, b)
Check if a == b, recursively if the arguments are of type Array or Object
Util.clamp (x, min, max)
Return 1 x clamped into 1 min and 1 max.
Util.fwdprovide (injectname, keys)
Create a Vue component provide() function that forwards selected properties.
Util.hyphenate (string)
Generate a kebab-case ('two-words') identifier from a camelCase ('twoWords') identifier
Util.weakid (object)
Fetch a unique id for any object.
Util.weakid_lookup (id)
Find an object from its unique id.
Util.join_classes (args)
Join strings and arrays of class lists from args.
Util.object_zip (obj, keys, values)
Assign obj[k] = v for all k of keys, v of values.
Util.object_await_values (obj)
Await and reassign all object fields.
Util.extend_property (prop, disconnector, augment)
Extend Ase.Property objects with cached attributs. Note, for automatic .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)
Extract the promise p state as one of: 'pending', 'fulfilled', 'rejected'
Util.compile_expression (expression, context)
Turn a JS $event handler expression into a function. This yields a factory function that binds the scope to create an expression handler.
Util.VueifyObject (object, vue_options)
VueifyObject - turn a regular object into a Vue instance. The object passed in is used as the Vue 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)
Produce hash code from a String, using an FNV-1a variant.
Util.split_comma (str)
Split a string when encountering a comma, while preserving quoted or parenthesized segments.
Util.escape_html (unsafe)
Properly escape test into &amp; and related sequences.
Util.parse_hex_color (colorstr)
Parse hexadecimal CSS color with 3 or 6 digits into [ R, G, B ].
Util.parse_hex_luminosity (colorstr)
Parse hexadecimal CSS color into luminosity.
Util.parse_hex_brightness (colorstr)
Parse hexadecimal CSS color into brightness.
Util.parse_hex_pgrey (colorstr)
Parse hexadecimal CSS color into perception corrected grey.
Util.parse_hex_average (colorstr)
Parse hexadecimal CSS color into average grey.
Util.parse_colors (colorstr)
Parse CSS colors (via invisible DOM element) and yield an array of rgba tuples.
Util.compute_style_properties (el, obj)
Retrieve a new object with the properties of obj resolved against the style of el
Util.inside_display_none (element)
Check if element or any parentElement has display:none
Util.is_displayed (element)
Check if element is displayed (has width/height assigned)
Util.wheel_delta (ev)
Retrieve normalized scroll wheel event delta in CSS pixels (across Browsers) This returns an object {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)
Use deltas from event to call scrollBy() on refs[scrollbars...].
Util.setup_shield_element (shield, containee, closer)
Setup Element shield for a modal containee. Capture focus movements inside containee, call closer(event) for pointer clicks on shield or when ESCAPE is pressed.
Util.swallow_event (type, timeout)
Use capturing to swallow any type events until timeout has passed
Util.popup_position (element, opts)
Determine position for a popup
Util.resize_canvas (canvas, csswidth, cssheight, fill_style)
Resize canvas display size (CSS size) and resize backing store to match hardware pixels
Util.dash_xto (ctx, x, y, w, d)
Draw a horizontal line from (x,y) of width w with dashes d
Util.hstippleRect (ctx, x, y, width, height, stipple)
Draw a horizontal rect (x,y,width,height) with pixel gaps of width stipple
Util.roundRect (ctx, x, y, width, height, radius, fill, stroke)
Fill and stroke a canvas rectangle with rounded corners.
Util.gradient_apply_stops (grad, stoparray)
Add color stops from stoparray to grad, stoparray is an array: [(offset,color)...]
Util.linear_gradient_from (ctx, stoparray, x1, y1, x2, y2)
Create a new linear gradient at (x1,y1,x2,y2) with color stops stoparray
Util.canvas_ink_vspan (font_style, textish)
Measure ink span of a canvas text string or an array
Util.midi_label (numish)
Retrieve the 'C-1' .. 'G8' label for midi note numbers
align8 (int)
Align integer value to 8.
Util.telemetry_subscribe (fun, telemetryfields)
Call fun for telemtry updates, returns unsubscribe handler.
Util.telemetry_unsubscribe (telemetryobject)
Call fun for telemtry updates, returns unsubscribe handler.
Util.in_keyboard_click()
Check if the current click event originates from keyboard activation.
Util.keyboard_click_event (fallback)
Retrieve event that triggers keyboard_click().
Util.keyboard_click (element, event, callclick)
Trigger element click via keyboard.
Util.in_array (element, array)
Check whether element is contained in array
Util.matches_forof (element, iteratable)
Check whether element is found during for (... of iteratable)
Util.element_text (element, filter)
Extract filtered text nodes from Element.
Util.dropdown (menu, event, options)
Popup menu using event.currentTarget as origin.
Util.clone_menu_icon (menu, uri, title)
Clone a menuitem icon via its uri.
Util.keyboard_map_name (keyname)
Retrieve user-printable name for a keyboard button, useful to describe KeyboardEvent.code.
Util.has_ancestor (element, ancestor)
Check if ancestor is an ancestor or element
Util.root_ancestor (element)
Retrieve root ancestor of element
Util.create_note (text, timeout)
Show a notification popup, with adequate default timeout
Util.markdown_to_html (element, markdown_text)
Generate element.innerHTML from markdown_text
Util.assign_async_cleanup (map, key, cleaner)
Assign map[key] = cleaner, while awaiting and calling any previously existing cleanup function
Util.observable_force_update()
Method to be added to a 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)
Join template literal arguments into a String
Util.strpad (string, len, fill)
Pad string with fill until its length is len
Util.hash53 (key, seed)
Generate 53-Bit hash from key.

2.4 Reference for ui/script.js

2.4.1 ScriptHost class

class ScriptHost

A ScriptHost object represents a Worker script in the Main thread.

A ScriptHost object (defined in script.js) runs in the Main Javascript thread. It starts a Worker via host.js which creates a WorkerHost object in the Worker thread. The ScriptHost<->WorkerHost objects bridge needed API from the Main thread to the Worker thread. The WorkerHost then loads and calls into a user provided ES Module, the actual user script which communicates via the WorkerHost global variable host. See also WorkerHost.

3 Appendix

 

3.1 One-dimensional Cubic Interpolation

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:

  1. \(f(x) = w_{3} x^3 + w_{2} x^2 + w_{1} x + w_{0}\)

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:

  1. \(f(0) = V_{1}\)
  2. \(f(1) = V_{2}\)
  3. \(f'(0) = V_{1}'\)
  4. \(f'(1) = V_{2}'\)

We obtain \(V_{1}'\) and \(V_{2}'\) from the respecting slope triangles:

  1. \(V_{1}' = \frac {V_{2} - V_{0}} {2}\)
  2. \(V_{2}' = \frac {V_{3} - V_{1}} {2}\)

With (6) → (4) and (7) → (5) we get:

  1. \(f'(0) = \frac {V_{2} - V_{0}} {2}\)
  2. \(f'(1) = \frac {V_{3} - V_{1}} {2}\)

The derivation of \(f(x)\) is:

  1. \(f'(x) = 3 w_{3} x^2 + 2 w_{2} x + w_{1}\)

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}\).

  1.   (3) → (1):   \(w_{3} + w_{2} + \frac {V_{2} - V_{0}} {2} + V_{1} = V_{2}\)
  2.   (5) → (10):   \(3 w_{3} + 2 w_{2} + \frac {V_{2} - V_{0}} {2} = \frac {V_{3} - V_{1}} {2}\)

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:

  1. \(f(x) = ((w_{3} x + w_{2}) x + w_{1}) x + w_{0}\)

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:

  1. \[\begin{aligned} f(x) \ = \ & V_{3} \ (0.5 x^3 - 0.5 x^2) \ + & \\ & V_{2} \ (-1.5 x^3 + 2 x^2 + 0.5 x) \ + & \\ & V_{1} \ (1.5 x^3 - 2.5 x^2 + 1) \ + & \\ & V_{0} \ (-0.5 x^3 + x^2 - 0.5 x) & \end{aligned}\]

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

  1. \(g(i) = V_{3} W_{3}[i] + V_{2} W_{2}[i] + V_{1} W_{1}[i] + V_{0} W_{0}[i]\)

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}\).

3.2 Modifier Keys

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:

GDK drag-and-drop modifier keys
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