Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Start from the template

The template plugin is a complete plugin you copy and change. It lives in OxiGuitar’s repository as docs/plugins/template/, and adds one of each common thing:

  • a format, “Example tab” (.extab): a tiny text format of eighth notes on a guitar, read and written;
  • a tab, “Statistics”, in the right area: how many notes each string of a track plays in the open score, as a small chart you can click, with a setting remembered in settings.json;
  • a service, note-names, which other plugins can call to name a pitch.

Each is one file in src/; you delete what you don’t need.

1. Copy it

Copy the folder, hidden .github/ included, to wherever you keep your projects. With git:

git clone --depth 1 --branch v0.9.0 https://github.com/mateusrdp/oxiguitar
cp -R oxiguitar/docs/plugins/template my-plugin
rm -rf oxiguitar
cd my-plugin

The template’s Cargo.toml takes the SDK and the contract suite from OxiGuitar’s repository on GitHub, at the same tag, v0.9.0 (Cargo downloads the repository the first time). The copy in OxiGuitar 0.9.0 still names v0.5.0: change both to v0.9.0, which has interface 1.3 (Interface versions). The template asks for interface 1.1, so what you build loads in OxiGuitar 0.2 and later; ask for 1.2 when you use its tools or the score version 2, and it needs 0.5; ask for 1.3 for a settings window, a section of the track dialog, footer links, the plugin’s folders, instruments, effects or audio devices, and it needs 0.9. To build for a later OxiGuitar, change both tags.

2. Build and test it

cargo test

The first run downloads and builds the SDK and the contract suite, which takes a minute. Then it runs:

  • the template’s own unit tests (src/*.rs);
  • the_manifest_matches_the_plugin, a test export_plugin! adds: the manifest is valid, its version is the crate’s, and every format, service and tab it declares is implemented, with contents the core can show;
  • tests/contract.rs, which builds the plugin file target/oxp/example.oxp and checks it through OxiGuitar’s own plugin host (Testing).

3. Try it in OxiGuitar

Copy the file into your plugin folder (OxiGuitar creates it the first time it starts):

# Linux
cp target/oxp/example.oxp ~/.local/share/oxiguitar/plugins/
# macOS
cp target/oxp/example.oxp ~/Library/Application\ Support/OxiGuitar/plugins/

On Windows, copy target\oxp\example.oxp to %APPDATA%\OxiGuitar\plugins\.

Start OxiGuitar. Tools → Plugins lists “Example”, not loaded yet. Open samples/open-strings.extab (File → Open): the plugin loads and reads it (oxiguitar-cli info samples/open-strings.extab also prints its import log, one warning). The right area has a Statistics tab, known from the manifest before the plugin loaded; open it, and click a bar of its chart.

4. Make it yours

  1. Rename it. In Cargo.toml, the package’s name. In manifest.json: id (lowercase letters and digits joined by . or -, starting with your own name: jane.chord-finder), name, description, authors, license and homepage. In build-oxp.sh, NAME (your file’s name) and LIBRARY (the package’s name with _ for -); in .github/workflows/build.yml, NAME again. In tests/contract.rs, the file’s name.
  2. Delete what you don’t need. Each contribution is a module, a line in src/lib.rs’s impl Plugin, and an entry in manifest.json. Remove all three; cargo test says if the manifest and the code disagree.
  3. Write your own. The next pages explain each part: the manifest, the SDK, formats, tabs, events, services, settings.
  4. Keep the versions equal. The version in manifest.json must be the crate’s, and you raise both with each release: when two files of the same plugin are installed, OxiGuitar uses the higher version.

What’s in the folder

FileWhat it is
Cargo.tomlThe crate: the oxp feature, the SDK, the contract suite for tests, lints, a release profile for small files
manifest.jsonWhat the plugin adds, read by OxiGuitar without loading the plugin
src/lib.rsThe plugin type, which hands out each contribution, and export_plugin!
src/format.rsThe Example tab format
src/statistics.rsThe Statistics tab
src/service.rsThe note-names service
src/notes.rsNote names and standard tuning, shared by the last two
tests/contract.rsBuilds the .oxp file and runs the contract suite on it
samples/open-strings.extabA file to open
build-oxp.shBuilds the release file for this system, dist/NAME-VERSION-SYSTEM-ARCH.oxp (Building for every system)
.github/workflows/build.ymlBuilds and tests the plugin for every system on GitHub, and attaches the files to a release

Here is src/lib.rs, the whole plugin in one place:

/// The plugin: built once, when OxiGuitar first uses one of its
/// contributions, and kept until the program ends.
pub struct Example {
    statistics: statistics::Statistics,
}

impl Plugin for Example {
    fn start(host: Host) -> Result<Example, Error> {
        Ok(Example {
            statistics: statistics::Statistics::new(host),
        })
    }

    // Each method hands out a contribution the manifest declares, by its id.

    fn reader(&self, format: &str) -> Option<&dyn FormatReader> {
        (format == format::ID).then_some(&format::ExampleTab as &dyn FormatReader)
    }

    fn writer(&self, format: &str) -> Option<&dyn FormatWriter> {
        (format == format::ID).then_some(&format::ExampleTab as &dyn FormatWriter)
    }

    fn service(&self, interface: &str, id: &str) -> Option<&dyn Service> {
        (interface == service::INTERFACE && id == service::ID)
            .then_some(&service::NoteNames as &dyn Service)
    }

    fn tab(&self, id: &str) -> Option<&dyn Tab> {
        (id == statistics::ID).then_some(&self.statistics as &dyn Tab)
    }

    fn event(&self, event: &oxiguitar_plugin::Event) {
        self.statistics.event(event);
    }
}

// The plugin's entry point and manifest, and a test (under `cargo test`)
// that the manifest matches the code.
oxiguitar_plugin::export_plugin!(Example);