Python 插件入门

TuneFlow 插件系统的核心思想是:开发者只需要关心数据模型,而不需要关心具体实现。

插件生命周期

TuneFlow 插件的唯一目标是修改歌曲数据,而 DAW 将会获取修改后的结果并自动应用所有改动。如图所示:

插件运行流程

插件的生命周期包括 5 个状态:installedcreatedrunningfinishederror

  • installed 插件已被安装到插件仓库中,但尚未被用户触发时的状态。

  • created 这是用户从插件仓库或右键菜单触发插件时的状态。此时,插件被添加到编辑面板,但插件需要的参数可能还没有被全部提供。

  • running 这是当用户点击插件控制面板上的启用按钮(或者如果所有参数在创建时已经被提供,并且该插件没有启用按钮),并开始运行插件时的状态。

  • finished 这是在插件修改了歌曲快照并且 DAW 基于修改后的结果创建了一个新快照之后的状态。插件随后被销毁并释放资源。

  • error 这是插件在running状态遇到错误时会转移至的状态。

运行流程

一般的插件运行流程为 installed -> created -> running -> finished。但有两个例外:

  1. 如果插件在running状态遇到错误,插件状态条会变为红色,插件会转入error状态。

  2. 如果用户选择在finishederror状态调整插件,则插件会返回到created状态。

定义插件

插件开发的基本单位是插件包(Plugin Bundle)。一个 Python 插件包由两个部分组成:插件包文件和插件文件。

插件包文件 (bundle.json)

插件包文件通常命名为bundle.json,其中包含此插件包中所有插件的信息。这里的信息将在用户加载插件代码之前显示给用户。

一个示例的bundle.json文件如下所示:

{
  "plugins": [
    ......,
    {
      "providerId": "my-provider-id",
      "providerDisplayName": "我的开发者名称",
      "pluginId": "my-plugin-id",
      "pluginDisplayName": "我的插件名称",
      "version": "1.0.0",
      "minRequiredDesktopVersion": "1.8.3",
      "options": {
        "allowReset": false
      },
      "triggers": [
        {
          "type": "context-track-content",
          "config": {
            "allowedTrackTypes": ["audio"]
          }
        }
      ],
      "categories": ["generate"]
    },
    ......
  ]
}

注意对于所有非 TuneFlow 内部的插件,我们都需要提供triggerscategories

triggers

它指定了插件可以在哪些元素的右键菜单中被用户运行。比如 context-track-content 代表可以从用户右键时的轨道的右键菜单运行。你可以指定多个 triggers,但实际执行的时候只会从某一个 trigger 触发。triggers 的取值请参考 TuneflowPluginTrigger在新窗口打开

插件运行时,插件的参数列表 (params) 中将会收到一个额外的 trigger参数。它将会包含用户触发这个插件时的上下文信息,以供插件了解应该处理歌曲中的哪些元素。

categories

它指定了该插件对应的类别,以便用户查找。categories 的取值请参考 TuneflowPluginCategory在新窗口打开

插件代码 (plugin.py)

在插件的根目录下,我们需要创建一个plugin.py文件,用于定义插件代码。你也可以将其他源代码放在同一目录下。当 TuneFlow 运行插件时,它会将插件的根目录添加到 PYTHONPATH中。

一个最简单的 Python 插件代码如下所示:

from tuneflow_py import TuneflowPlugin, Song, ParamDescriptor


class HelloWorld(TuneflowPlugin):
    @staticmethod
    def provider_id():
        return "andantei"

    @staticmethod
    def plugin_id():
        return "hello-world"

    @staticmethod
    def params(song: Song) -> dict[str, ParamDescriptor]:
        return {}

    @staticmethod
    def run(song: Song, params: dict[str, Any]):
        print("Hello World!")

提示

这里的所有方法都是静态方法。这是有意为之的:整个插件应该是无状态的(stateless),一个插件执行的结果仅由输入决定,而不受插件本身任何内部状态的影响。

在编写插件时,我们的主要关注点是paramsrun方法。

params

当用户触发插件时(即installed -> created),将调用此方法。你应该在此返回你希望从用户或 DAW 获取的输入参数。DAW 将用你返回的配置生成你的插件 UI。

额外地,你可以使用 song 来获取项目当前快照的信息,以便定制你的参数。例如,如果你有一个适用于不同拍号的预设列表,你可以使用song来读取当前歌曲的拍号,并在返回参数列表时过滤掉不适用于歌曲的选项。

run

当用户通过点击 启用 按钮实际运行插件时 (即 created -> running),DAW 将调用此方法。

这里是你实现主要逻辑的地方。该方法接收当前歌曲的快照 (song: Song),以及根据你提供的参数列表,从用户或 DAW 那里收集到的参数值 (params)。

下一步

现在我们已经了解了插件的基本生命周期和结构,让我们实际开始构建一个插件吧!继续阅读 Python 插件开发指南