Plugin Patterns and Copy-Paste Examples

1. Menu-only plugin

public sealed class MenuOnlyPlugin : IPlugin
{
    private IHostContext? _context;

    public string Id => "menu-only";
    public string DisplayName => "Menu Only";
    public Version Version => new(1,0,0);

    public void OnLoad(IHostContext context)
    {
        _context = context;
        context.WindowManager.AddMenuContribution(
            10,
            "Tools.100.Menu Only.Run",
            _ => Execute(),
            () => _context?.DocumentService.ActiveDocument is not null);
    }

    public void OnUnload() => _context = null;

    private void Execute() { /* do work */ }
}

2. Docked pane plugin

public sealed class DockPanePlugin : IPlugin, IProvidesDockPanes
{
    private IHostContext? _context;
    private Label? _label;

    public string Id => "dock-pane";
    public string DisplayName => "Dock Pane";
    public Version Version => new(1,0,0);

    public void OnLoad(IHostContext context)
    {
        _context = context;
        foreach (var pane in GetDockPanes())
        {
            context.WindowManager.AddDockPane(pane);
        }

        context.WindowManager.AddMenuContribution(90, "View.pluginsgroup.Show Dock Pane", c => c.WindowManager.SetPaneVisible("dock-pane.main", true));
        context.SampleControllerBus.StateChanged += OnStateChanged;
    }

    public void OnUnload()
    {
        if (_context is not null)
        {
            _context.SampleControllerBus.StateChanged -= OnStateChanged;
        }

        _context = null;
        _label = null;
    }

    public IEnumerable<DockPaneRegistration> GetDockPanes()
    {
        yield return new DockPaneRegistration(
            "dock-pane.main",
            "Dock Pane",
            DockPlacement.Right,
            CreateView,
            AllowVerticalNeighbour: true,
            AllowHorizontalNeighbour: true,
            CanUndock: true);
    }

    private object CreateView()
    {
        _label = new Label { Dock = DockStyle.Fill, TextAlign = ContentAlignment.MiddleCenter, Text = "Ready" };
        return new Panel { Dock = DockStyle.Fill, Controls = { _label } };
    }

    private void OnStateChanged(object? sender, SampleControllerState state)
    {
        if (_label is null) return;
        if (_label.InvokeRequired)
        {
            _label.BeginInvoke(new Action(() => OnStateChanged(sender, state)));
            return;
        }

        _label.Text = $"Cursor {state.Cursor}";
    }
}

3. Effect dialogue with preview/apply/cancel

private void OpenEffectDialog()
{
    var state = _context.SampleControllerBus.State;
    var start = Math.Min(state.SelectionStart, state.SelectionEnd);
    var endExclusive = Math.Max(state.SelectionStart, state.SelectionEnd);
    if (start == endExclusive)
    {
        start = state.ViewStart;
        endExclusive = state.ViewStart + state.ViewLength;
    }

    var source = _context.DocumentService.CopyEffectiveRangeToBuffer(start, endExclusive);
    using var preview = _context.DocumentService.BeginPreview(start, endExclusive);

    using var dlg = new MyEffectDialog();

    void rebuildPreview()
    {
        var previewBuffer = Process(source, dlg.Strength);
        preview.ApplyPreview(previewBuffer);

        if (dlg.PreviewEnabled)
        {
            _context.AudioSystemBus.RequestPlayback(new PlaybackCommand(
                PlaybackCommandType.PlayBuffer,
                Buffer: previewBuffer,
                StartFrame: 0,
                EndFrameExclusive: previewBuffer.InterleavedSamples.Length / previewBuffer.Channels,
                Loop: true,
                PreviewOwnerId: "my-effect.preview"));
        }
    }

    dlg.SettingsChanged += (_, _) => rebuildPreview();

    var result = dlg.ShowDialog();

    _context.AudioSystemBus.RequestPlayback(new PlaybackCommand(PlaybackCommandType.Stop, PreviewOwnerId: "my-effect.preview"));

    if (result == DialogResult.OK)
    {
        preview.Commit();
    }
    else
    {
        preview.Cancel();
    }
}

4. Context menu extension (sample editor)

context.WindowManager.AddContextMenuContribution(
    surfaceId: "SampleEditor.Wave",
    sort: 200,
    menuPath: "effectsgroup.My Effect",
    callback: _ => OpenEffectDialog(),
    isEnabled: () => context.DocumentService.ActiveDocument is not null);

5. Preset-enabled pattern

private const string PresetKey = "MyEffect";

private void SavePreset(string name, string payloadJson)
{
    if (_context.PresetStore.PresetExists(PresetKey, name))
    {
        if (MessageBox.Show($"Overwrite preset '{name}'?", "Confirm", MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes)
        {
            return;
        }
    }

    _context.PresetStore.SavePreset(PresetKey, name, payloadJson);
    RefreshPresetList();
}

private void LoadLastUsedIfAny()
{
    var name = _context.PresetStore.LastUsed(PresetKey);
    if (string.IsNullOrWhiteSpace(name)) return;
    var json = _context.PresetStore.LoadPreset(PresetKey, name);
    if (string.IsNullOrWhiteSpace(json)) return;
    ApplyPreset(json);
}
Persist functional settings only. Do not persist preview/bypass/crosshair UI states.

6. Codec plugin pattern

public sealed class MyCodecPlugin : IPlugin
{
    public string Id => "my-codec";
    public string DisplayName => "My Codec";
    public Version Version => new(1,0,0);

    public void OnLoad(IHostContext context)
    {
        context.CodecRegistry.Register(new MyCodec());
    }

    public void OnUnload() { }

    private sealed class MyCodec : IFileTypeCodec
    {
        public string CodecId => "my-codec.format";
        public string DisplayName => "My Codec";
        public IReadOnlyList<string> Extensions => new[] { ".mywav" };

        public SampleBuffer Read(string filePath)
        {
            // decode to interleaved float[]
            throw new NotImplementedException();
        }

        public void Write(string filePath, SampleBuffer buffer)
        {
            // encode from interleaved float[]
            throw new NotImplementedException();
        }
    }
}

7. Delegates and callback function shapes

APIFunction type
AddMenuContribution(...)Action<MenuCommandContext>, Func<bool>?
AddContextMenuContribution(...)Action<MenuCommandContext>, Func<bool>?
StateChanged (sample bus)EventHandler<SampleControllerState>
SelectionChangedEventHandler<SelectionChange>
PlaybackStateChangedEventHandler<PlaybackState>

Back to top