Plugins¶
Phonometrica can be extended with plugins, which are written in Phonometrica’s scripting language.
When it starts up, Phonometrica loads all plugins which are located in the system plugin directory or in the user plugin directory. Plugins
can add functionality to Phonometrica in a number of ways, but most commonly they will create a submenu in the Plugins menu which provides
additional commands offered by the plugin and/or custom coding protocols.
Plugins can be shared and redistributed as ZIP files. To install a plugin, go to Plugins > Install plugin... and choose the ZIP
file corresponding to the plugin you wish to install. It will be installed in the current user’s plugin directory.
See Scripting to learn more about scripting. If you are updating a plugin written for a version of Phonometrica older than the current scripting engine, see Migrating old scripts for the list of language changes.
Structure of a plugin¶
To be valid, a plugin must adhere to a number of conventions. A plugin usually contains the following items:
a description file, named
description.phon(compulsory)a script named
initialize.phon(optional). If it exists, it will be run after reading the description file.a script named
finalize.phon(optional). If it exists, it will be run when the program exits.a
Scriptssub-directory, which contains all your scripts (optional).a
Protocolssub-directory, which contains coding protocols (optional)a
Resourcessub-directory, which may contain anything (optional).a
Documentationsub-directory, which may contain HTML pages that menu actions can open (optional).
Plugins are loaded in alphabetical order, system plugins first, then user plugins. To temporarily disable
a plugin without uninstalling it, create an empty file named ignore.txt at the root of the plugin’s
directory: Phonometrica will skip it at startup.
The description file (description.phon) is a script written in Phonometrica’s scripting language: it is
executed when the plugin is loaded, and its result (the value of its last expression) must be a table
containing all the information necessary to initialize the plugin. In practice, the file usually consists
of a single table literal. It must at least contain a name.
{
"name": "My first plugin"
}
Here is a more realistic (and useful) example:
{
"name": "PFC",
"version": "0.1",
"description": [
"Plugin for the PFC project.\n",
"See http://www.projet-pfc.net"
],
"actions": [
{ "name": "Add metadata", "target": "add_metadata.phon" }
]
}
The version field can be used to distinguish different versions of the plugin, for instance if a later update breaks backward compatibility. The
description field is displayed in the About entry in the plugin’s menu (if it exists). This field can be a simple string or, as in the example
above, a list of strings (which are concatenated).
The optional field named actions maps to a list of tables: Each action describes a menu entry, and must have two fields: a name key,
which is the entry’s label (as it is displayed in the menu), and a target field, which usually gives the name of a script (located in the plugin’s
Scripts sub-directory) which will be executed when the menu entry is clicked on. If the target ends with .html instead, it names a page in
the plugin’s Documentation sub-directory, which is opened in the help browser when the entry is clicked on.
Note
Since the description file is a regular script, # comments are allowed, and values may be computed
(for instance, a version string stored in a variable and reused). The only requirement is that the last
expression in the file evaluates to the description table.
Warning
In the scripting language, double-quoted strings interpolate expressions between braces ("{...}")
and process escape sequences. A literal brace in a double-quoted string must therefore be written
\{. Alternatively, use single-quoted strings, which are raw (no interpolation and no escapes) —
this matters mostly for regular expressions in coding protocols (see below).
Plugins written for older versions of Phonometrica used a JSON file named description.json. That
file is no longer read: Phonometrica will refuse to load the plugin and ask for the file to be renamed
to description.phon. The JSON syntax for tables and lists is essentially unchanged, so the conversion
usually amounts to renaming the file (and escaping any literal braces inside double-quoted strings).
Initialization, finalization and startup scripts¶
If the plugin contains a script named initialize.phon, it is run immediately after the description file has
been read. This is the place to set up state shared by the plugin’s actions or to connect to events (see below).
Variables and functions declared at the top level of initialize.phon are visible to the plugin’s action
scripts, which run later in the same scripting session.
The plugin’s Scripts sub-directory is added to the module search path when the plugin is loaded, so any
script file it contains can also be loaded as a module with import, by name and without extension: an
action script can run import mytools to load Scripts/mytools.phon.
If the plugin contains a script named finalize.phon, it is run when Phonometrica exits, which gives the
plugin a chance to do some cleanup.
In addition to plugins, Phonometrica also runs standalone startup scripts: every file located in the
Scripts directory of the user settings directory (next to the Plugins directory) is executed at
startup, in alphabetical order. This is a convenient way to customize Phonometrica without creating a
full-fledged plugin.
A plugin’s scripts have access to the whole scripting API, including GUI functions such as info(),
warning(), alert() and ask(), and the phon namespace (e.g. phon.project). For example,
an action script might look like this:
var annot = get_current_annotation()
if annot then
info("Current annotation: " & annot.path)
else
alert("No annotation is currently open")
end
Two functions are specifically useful in plugin scripts: get_plugin_version(name) returns the version
string of the plugin called name (or null if there is no such plugin), and
get_plugin_resource(plugin_name, file_name) returns the path of the file file_name in the
Resources sub-directory of the plugin plugin_name.
Reacting to events¶
Phonometrica provides a simple signal/slot mechanism (also known as event/callback), implemented in the
signal library that is preloaded at startup. A signal is a unique string identifier; any number of
functions (slots) can be connected to it. The following functions are available:
create_signal()creates and returns a new signal identifier.connect(id, slot)connects the functionslotto the signalid.disconnect(id, slot)disconnectsslotfromid.emit(id, arg)emits the signalid: each connected slot is called in turn withargas its argument. The values returned by the slots are collected into a list, which is returned byemit.emit(id)emits the signal withnullas the argument.
Phonometrica itself emits a number of signals as the project changes. The corresponding identifiers are available as global constants:
SIGNAL_PROJECT_LOADED: a project was loaded (slots receivenull)SIGNAL_ANNOTATION_LOADED: an annotation was added to the project (slots receive theAnnotation)SIGNAL_SOUND_LOADED: a sound was added to the project (slots receive theSound)SIGNAL_ANNOTATION_IMPORTED: an annotation was imported (slots receive theAnnotation)SIGNAL_SOUND_IMPORTED: a sound was imported (slots receive theSound)SIGNAL_SCRIPT_LOADED: a script was added to the project (slots receive theDocument)SIGNAL_DATASET_LOADED: a dataset was added to the project (slots receive theDocument)
A plugin typically connects to these events in its initialize.phon script. For example, the following
code displays a message box every time an annotation is loaded into the project:
function on_annotation_loaded(annot)
info("Loaded annotation " & annot.path)
end
connect(SIGNAL_ANNOTATION_LOADED, on_annotation_loaded)
Plugins can also define their own events to decouple their components:
var my_signal = create_signal()
function greet(name)
return "Hello " & name & "!"
end
connect(my_signal, greet)
var results = emit(my_signal, "world")
Defining coding protocols¶
If you have devised a coding scheme for your data, Phonometrica lets you define a coding protocol. A coding protocol is a description of your coding scheme which offers a user-friendly interface for querying your data; it tells Phonometrica what to look for and how to present the information to the user in the query editor. Phonometrica will automatically load all valid coding protocols in your plugin’s submenu.
A coding protocol defines a number of fields which can take on a number of values. The user is presented with a number of checkboxes for each
field, and Phonometrica converts the query to the corresponding regular expression, as defined by the coding protocol. Like the description
file, a coding protocol is a script whose last expression evaluates to a table: every file in the plugin’s Protocols sub-directory is run
through the scripting engine when the plugin is loaded. Here is a simple yet realistic example, drawn from the
PFC project:
Note
Writing a coding protocol by hand is not strictly necessary. The protocol builder dialog (available from
Plugins > Build coding protocol... or from the right-click menu of any concordance column) provides an
interactive editor with live preview and saves the result as a file that can be dropped into a plugin’s
Protocols/ directory. Hand-editing remains useful when you need features that the builder does not expose
(for instance the layer_index, layer_field, or fields_per_row attributes described below), or when
you want to version-control the protocol source directly. See Applying coding protocols for more on
using the builder. The builder escapes braces for you, so a quantifier such as [aeiou]{2} typed into a
field is written to the file as "[aeiou]\{2\}" and read back unchanged; when writing a protocol by hand,
the escaping is yours to do (or use single-quoted strings, which are raw).
{
"type": "coding_protocol",
"name": "Schwa coding",
"version": "0.1",
"separator": "",
"layer_index": 2,
"fields_per_row": 3,
"fields": [
{"name": "Spelling", "match_all": ".",
"values": [
{"match": "e", "text": "graphical e"},
{"match": "[^e]", "text": "no e"}
]
},
{"name": "Schwa", "match_all": "[0-2]",
"values": [
{"match": "0", "text": "Absent"},
{"match": "1", "text": "Present"},
{"match": "2", "text": "Uncertain"}
]
},
{"name": "Position", "match_all": "[1-5]",
"values": [
{"match": "1", "text": "monosyllable"},
{"match": "2", "text": "initial syllable"},
{"match": "3", "text": "median syllable"},
{"match": "4", "text": "final syllable"},
{"match": "5", "text": "metathesis"}
]
},
{"name": "Left context", "match_all": "[1-5]",
"values": [
{"match": "1", "text": "vowel"},
{"match": "2", "text": "consonant"},
{"match": "3", "text": "start of an intonational phrase"},
{"match": "4", "text": "uncertain vowel"},
{"match": "5", "text": "simplified cluster"}
]
},
{"name": "Right context", "match_all": "[1-4]",
"values": [
{"match": "1", "text": "vowel"},
{"match": "2", "text": "consonant"},
{"match": "3", "text": "weak prosodic boundary"},
{"match": "4", "text": "strong prosodic boundary"}
]
}
]
}
The type field is required and indicates that this file is a coding protocol. The name field corresponds to the name of the grammar, as it
will be seen by the user, and version is an optional field which corresponds to the version of the protocol.
Next, the separator is an optional attribute which indicates the separator to be used between fields. In this case, it is an empty string,
which means that the fields are concatenated directly (e.g. 1412). If the separator
was _, for instance, each field should be separated by this symbol (e.g. 1_4_1_2).
Next the layer_index attribute indicates the index of the layer in which codings
should be searched for. The default value is 0, which means that codings are searched for in all
annotation layers.
The following attribute, fields_per_row, lets us specify how many fields should be displayed in a row. In our case, since there are 5
fields, we decide to distribute them across 2 rows. The default value is 3.
Finally, the fields attribute contains a list of fields, each of them corresponding to a table. The name attribute provides a
descriptive label for the field. The match_all attribute is a partial regular expression that should match all possible values for the field. If a
user doesn’t check any value for a field, this attribute will be used to retrieve all possible values. The values attribute contains a list
of values. Each of them contains (at least) a match attribute, which is a string corresponding to the value, and a text attribute which
is the label that will be displayed in the user interface for the corresponding value, along with a check box. Note that leaving all values unchecked
has the same effect as checking them all.
Warning
Because protocol files are parsed by the scripting engine, a regular expression that contains braces —
for instance a counted repetition such as [0-9]{2} — must not be written as-is in a double-quoted
string: the {2} part would be interpreted as string interpolation. Either escape the braces
("[0-9]\{2\}") or, preferably, use a single-quoted raw string ('[0-9]{2}'), in which every
character stands for itself.