Documentation · Developers

Build your first package

A Transcend extension is a folder containing a manifest and a Lua script. In about ten minutes you'll write one that adds two commands to the command palette and logs every time playback starts.

Before you start

Extensions are on in every copy of Transcend, so there is nothing to switch on. This page uses scripting API level 13, the level this beta implements. The API may still change before Transcend 1.0; any change comes with release notes showing the before and after.

Only want new colours or a new arrangement of panels? That needs no code at all: see Themes and layouts.

1Create the package

  1. In Transcend, open Settings ▸ Packages ▸ Installed and click New Package....
  2. Name it Octave Tools and check the author name. The id fills itself in, such as com.yourname.octave-tools. It's reverse-DNS, like a Java package or an Apple bundle id, and it's how Transcend recognises your package, so keep it once you share it.
  3. Leave the location as Documents/Transcend Extensions or choose your own, then click Create.

Transcend creates the folder and loads it straight away. To check it's running, press Ctrl+P (Windows) or Cmd+P (macOS) and run Octave Tools: Say Hello; its message appears under Settings ▸ Packages ▸ Console.

com.yourname.octave-tools/
├── manifest.json        what the package is
├── README.md
├── scripts/
│   └── main.lua         your code
├── .luarc.json          editor set-up (never exported)
├── .transcend/          API definitions for autocomplete (never exported)
└── .vscode/             suggests the Lua extension (never exported)

The package shows as Linked in the list: Transcend runs it straight from this folder, never changes or deletes it, and Unlink only stops loading it.

Using a folder you made yourself

A package folder only needs manifest.json and scripts/main.lua. If you'd rather create them by hand, as shown in steps 3 and 4, click Load Folder... and choose the folder. Transcend checks it and shows the same prompt a user sees when installing it, listing what it asks for, before loading it.

If you add scripts or permissions later

Transcend remembers what was approved for a linked folder. If it later gains scripts or a new permission, it stops loading and asks you to choose Load Folder... on it again to review the change. This protects people who link a shared or synced folder that someone else can edit.

2Open it in your editor

Any text editor works. We recommend Visual Studio Code (free, Windows and macOS):

  1. In VS Code, choose File ▸ Open Folder... and open your package folder.
  2. VS Code offers to install the Lua extension (by sumneko). Accept.
  3. Open scripts/main.lua and type transcend.. Every function Transcend offers appears with its parameters and a short description, and a misspelled call is underlined before you ever run it.

The autocomplete comes from .transcend/transcend.d.lua, which New Package writes for you. For a folder you made by hand, download transcend.d.lua, put it in a .transcend folder inside your package, and add a .luarc.json next to manifest.json:

{
  "runtime.version": "Lua 5.4",
  "runtime.path": ["scripts/lib/?.lua"],
  "runtime.builtin": { "io": "disable", "debug": "disable", "package": "disable" },
  "workspace.library": [".transcend"]
}

The runtime.builtin line makes the editor flag io, debug and package, which don't exist inside Transcend's sandbox.

3The manifest

New Package wrote this for you; it describes the package to Transcend. Edit the description if you like.

manifest.json

{
  "format": "transcend.package",
  "formatVersion": 1,
  "id": "com.yourname.octave-tools",
  "name": "Octave Tools",
  "version": "0.1.0",
  "author": "Your Name",
  "description": "Counts and transposes the notes in the open pattern.",
  "apiLevel": 3,
  "provides": ["scripts"],
  "entry": "scripts/main.lua"
}
FieldNotes
format, formatVersionAlways exactly as shown.
idLetters, digits, dots, dashes and underscores only. Never change it after you release. The id is how Transcend recognises an update and keeps the package's saved data.
name, version, authorRequired. The name and version can change freely between releases.
descriptionOptional. One line, shown in the package list.
apiLevelThe lowest scripting API level your script needs. This beta implements level 13; this example needs 3 for actions.register. Newer versions of Transcend keep supporting every lower level.
providesWhat the package contains. It must match the folder exactly: declare "scripts" without a scripts/ folder, or ship one without declaring it, and the install is refused.
entryThe script that runs first. Must be inside the package.
permissionsLeave it out unless you need one, such as ["midi.out"]. Users see this list before installing.
homepage, requiresAppOptional. requiresApp takes a version range such as ">=0.7.0".

4Write the script

Replace everything in scripts/main.lua with the code below. Everything at the top level runs once each time the package starts: on install, on enable, on every launch, when you press Reload Scripts, and each time you save a file in a linked folder.

scripts/main.lua

-- Octave Tools: two commands for the open pattern, plus a playback log.
print("Octave Tools loaded")

-- The pattern open in the piano roll, or nil. Nothing is open in a fresh
-- project, so always check.
local function openPattern()
    local id = transcend.project.activePattern()
    if id == nil or id == "" then
        print("Open a pattern in the piano roll first.")
        return nil
    end
    return id
end

transcend.actions.register("countNotes", "Octave Tools: Count Notes", function()
    local id = openPattern()
    if not id then return false end
    local notes = transcend.project.notes(id) or {}
    print(string.format("%d notes in the open pattern", #notes))
    return true
end)

transcend.actions.register("octaveUp", "Octave Tools: Up an Octave", function()
    local id = openPattern()
    if not id then return false end

    local moved = {}
    for _, n in ipairs(transcend.project.notes(id) or {}) do
        moved[#moved + 1] = {
            note        = math.min(127, n.note + 12),
            velocity    = n.velocity,
            startStep   = n.startStep,
            lengthSteps = n.lengthSteps,
            pluginId    = n.pluginId,
        }
    end

    -- Scripts never edit the project directly. They ask Transcend to run an
    -- action, which is what puts the change on the undo stack.
    local ok = transcend.actions.invoke("pattern.setNotes", { patternId = id, notes = moved })
    if not ok then
        print("Transcend declined the edit. Is the pattern placed on the arrangement?")
    end
    return ok
end, { undoable = true })

transcend.events.on("transportStarted", function(e)
    print(string.format("Playback started at %.2f s, %.1f BPM", e.seconds, e.tempo))
end)

Three things to notice:

5Reload and try it

  1. Save main.lua. Because the folder is linked, Transcend reloads the package by itself a moment later. (You can also select Octave Tools in Settings ▸ Packages and click Reload Scripts.) The Console tab shows Octave Tools loaded.
  2. Open a pattern with some notes in the piano roll. For Up an Octave, the pattern must be placed on the arrangement.
  3. Press Ctrl+P (Windows) or Cmd+P (macOS) and type octave. Both commands are there.
  4. Run Octave Tools: Up an Octave. The notes jump up. Press Ctrl/Cmd+Z and the whole edit undoes in one step.
  5. Press play and check the Console for the playback line.

Your commands also appear in Settings ▸ Shortcuts, where they can be bound to a key.

6Edit and reload

Change main.lua in your editor and save. Transcend notices the change and reloads the package a moment later. There's no build step, no copying and no button to press: Transcend runs the folder you're editing. If you save something that breaks the manifest, the package stops and the Console says why.

7Test it

Transcend can run your extension's tests by itself, with no window and no sound, against a pretend project. Put test files in a tests folder next to manifest.json. Each file is a list of tests:

tests/octave_test.lua

test("Up an Octave moves every note up 12", function()
    local before = transcend.project.notes("p1")
    testkit.truthy(testkit.run("octaveUp"))             -- run your own command
    testkit.equal(#testkit.calls("pattern.setNotes"), 1) -- what it asked Transcend to do
    local after = transcend.project.notes("p1")
    testkit.equal(after[1].note, before[1].note + 12)   -- and the result
end)

Then run Transcend with --test-extension and your package folder:

Windows

& "C:\Program Files\Transcend\Transcend.exe" --test-extension . | Out-Host

Run it from inside the package folder in PowerShell. Transcend is a window program, so its report only shows when its output goes through a pipe, which is what | Out-Host does.

macOS

/Applications/Transcend.app/Contents/MacOS/Transcend --test-extension . | cat

Run it from inside the package folder in Terminal.

It prints a line per test and exits with 0 when everything passed, 1 when a test failed and 2 when nothing could run, so it works in an automated check too.

The API reference lists every testkit helper, and testkit.d.lua gives them autocomplete. Your saved state goes to a scratch folder during a test run, never to your real packages.

8Package it as a .tpkg

Select your package in Settings ▸ Packages and click Export .tpkg.... Transcend packs the folder and leaves out things that don't belong in a package, such as .git, .vscode and .DS_Store. It then checks the file with the same rules the installer uses. If the export succeeds, the file will install.

Zipping by hand instead

A .tpkg is a zip file with a different extension. If you'd rather make one yourself, two things trip people up:

Zip the folder's contents, not the folder

manifest.json must be at the top of the archive. Zipping the folder itself puts it one level down, and the install is refused with "no manifest.json at its root".

The archive must also use forward slashes. Windows PowerShell 5.1's Compress-Archive writes backslashes, and Transcend refuses them for safety. Use one of the methods below instead.

Windows

Open the package folder, select everything inside it (Ctrl+A), right-click, and choose Send to ▸ Compressed (zipped) folder (Windows 10) or Compress to ZIP file (Windows 11). Rename the result from .zip to .tpkg. You may need to turn on View ▸ File name extensions first.

With 7-Zip, from inside the folder:

7z a -tzip ..\com.yourname.octave-tools.tpkg *

macOS

In Terminal, from inside the package folder:

zip -r -X ../com.yourname.octave-tools.tpkg . -x ".*" -x "*/.*"

The -x patterns leave out .DS_Store and other hidden files. Finder's Compress adds a __MACOSX folder, so the command line is the cleaner choice.

To test the result the way a user would, Unlink your folder first (Transcend won't install a package whose id is currently linked), then drag the .tpkg onto the Transcend window.

9Share and publish it

There are three ways to get your extension to other people, from quickest to widest.

Send the file

A .tpkg installs by dragging it onto the Transcend window, or with Settings ▸ Packages ▸ Install Package.... Send it any way you like.

Install from a link

Put your extension on GitHub and attach the .tpkg to a release. Anyone can then install it by pasting your-name/your-repo, or the release's address, into the Install from a link box. The box is in Browser ▸ Extensions ▸ Get Extensions and at the top of the Store (View ▸ Get Extensions...). Transcend finds the newest release, downloads its .tpkg, and shows the person what it asks for before installing.

On the Get Extensions shelf

The shelf, and the larger Store, list extensions in two sections:

The easiest way into Community is the extension template on GitHub: transcend-extension-template. Click Use this template and you get an example extension with tests, a script that runs them in your installed Transcend, and an automatic check that builds the .tpkg and attaches it to a release every time you push a version tag (git tag v1.0.0, then push the tag). To be listed, add one small file naming your repository to the extensions index in a pull request. The pull request runs the same checks the shelf does. The template's README walks through every step.

What the shelf checks: the manifest is valid and names what is really in the folder, a script names an API level Transcend has, no file is over 4 MB and the package is under 64 MB, and a Community extension does not ask for network access. Give it a shelf.png (16:9, at least 480×270) and the shelf card shows it.

Updates

10What a script can reach

Everything is on one global table, transcend. The full API reference lists every function and the exact data each event receives.

TableWhat it's forLevel
projectRead the tempo, track count, project name, patterns and notes. From level 5, also every track (name, mute, solo, volume, pan), every clip on the arrangement, the playhead and whether it's playing. From level 6, also the Samples bin. From level 7, also every loaded plugin, where it sits, and its parameters. From level 11, which instrument a pattern plays and which one the user is playing now. From level 12, the musical context: the tempo, beat and bar at any point of the song, the time signature, the song's key, markers, the loop, what is selected, and a pattern's controller lanes.1, 5, 6, 7, 11, 12
actionsRun any Transcend command by id, and register your own for the palette. From level 5, the commands include moving, deleting, muting and selecting clips, renaming tracks, setting their mute, solo, volume and pan, and moving the playhead. From level 6, they also create things: new tracks, new patterns, and pattern and audio clips placed on the arrangement. From level 7, they also set a plugin's parameters. From level 8, they launch and stop clips in the clip launcher. From level 9, they play a note on an instrument for a moment, so a tool can let the user hear a chord before writing it. From level 11, they give a pattern an instrument.1, 3, 5, 6, 7, 8, 9, 11
stateSave small values (strings, numbers, booleans) that persist.1
filesRead files bundled inside your own package.1
eventsReact to playback starting and stopping, the playhead moving, clips and tracks being added or removed, notes played on a MIDI keyboard, projects loading and saving, and more. From level 12, also notes changing in a pattern, a different pattern being opened, and the tempo, time signature, key or markers changing, so a tool can listen instead of checking every frame.2, 12
midiMap controller knobs and pads to commands (no permission needed), and send MIDI to hardware (needs midi.out).2
panelsDraw your own view in a dock tab, and handle clicks, drags and keys on it. From level 7, the view can instead be a card on the Browser's home screen, or a whole page beside the arrangement, piano roll and mixer, with its own button in the main bar. From level 10, it can be a floating window, and your code can open it. From level 13, a page can open the mixer under itself and line its own drawing up with the mixer's channel strips, and draw a small picture of any clip.4, 7, 10, 13
menusAdd your own commands to the right-click menus of clips, track headers and mixer strips, and to the piano roll's Tools menu. Your command is told what was right-clicked.10
api, app, permissionsCheck what this version of Transcend supports and what your package was granted.1

11Rules worth knowing